Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistencies with conditional noexcept and overloads

I have an issue which is quite similar to this one.

In short, I have a magic method, which is noexcept if another method is noexcept.

The weird thing is that this "another method" has two overloads, and the compiler chooses the second overload to determine magic noexcept-ness.

However, when magic is called later on, the first overload is called, but the noexcept-ness of magic remains the same!

Here is the wandbox link

From what I understand:

  1. noexcept(magic(dummy2{})) calls
  2. noexcept(noexcept(adl_caller(...)) which falls back to
  3. adl_caller(..., priority_tag<0>) noexceptsince user_method(dummy2) is not known by the compiler at this point.

Fair enough, however, how is user_method(dummy2) called 3 lines above? Is this intended by the Standard?

Sorry if I'm not clear enough.

#include <iostream>

template <unsigned N> struct priority_tag : priority_tag<N - 1> {};
template <> struct priority_tag<0> {};

template <typename T>
auto adl_caller(T t, priority_tag<1>) noexcept(noexcept(user_method(t)))
    -> decltype(user_method(t)) {
  std::cout << "first adl_caller overload" << std::endl;
  user_method(t);
}

// tricky noexcept ...
template <typename T> void adl_caller(T, priority_tag<0>) noexcept {
  std::cout << "second adl_caller overload" << std::endl;
}

template <typename T>
void magic(T t) noexcept(noexcept(adl_caller(t, priority_tag<1>{}))) {
  adl_caller(t, priority_tag<1>{});
}

struct dummy {};
struct dummy2 {};

// un-commenting this line makes the above call to cout print '0'
// void user_method(dummy2);

void user_method(dummy)
{
  // user_method(dummy2) is declared after this point
  // this line prints '1', since magic falls back to the second adl_caller overload
  std::cout << "noexcept?: " << noexcept(magic(dummy2{})) << std::endl;
  std::cout << "dummy method called" << std::endl;
  // however, the first adl_caller overload is called here ...
  magic(dummy2{});
}

void user_method(dummy2)
{
  std::cout << "dummy2 method called" << std::endl;
}

int main()
{
  magic(dummy{});
}
like image 602
Dante Avatar asked May 03 '17 15:05

Dante


1 Answers

[temp.point]/8:

A specialization for a function template [...] may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

Compare [temp.dep.candidate]:

For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) except that:

  • For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.

  • For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

like image 103
T.C. Avatar answered Nov 15 '22 15:11

T.C.