Search code examples
c++templateslanguage-lawyerargument-dependent-lookupdependent-name

Best match not found by ADL after point of instantiation. Is this UB?


Consider the following code, in which the location of the overloads of f causes some non-intuitive behaviour. The code compiles with no warnings in both Clang 3.4.1 and gcc 4.8.

template<typename T>
struct A
{
    static const int value = sizeof(f(T()));
};

struct B
{
};

struct D : B
{
};

char f(B);

// instantiates A<D>, unqualified name lookup finds f(B) via ADL
static_assert(A<D>::value == sizeof(f(B())), ""); // passes

long f(D); // but wait, f(D) would be a better match!

// A<D> is already instantiated, f(D) is not found
static_assert(A<D>::value == sizeof(f(B())), ""); // passes

The C++11 standard suggests that the above code invokes undefined behaviour:

[temp.dep.candidate]

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules except that:

  • For the part of the lookup using unqualified name lookup or qualified name lookup, only function declarations from the template definition context are found.
  • For the part of the lookup using associated namespaces, only function declarations found in either the template definition context or the template instantiation context are found.

If the function name is an unqualified-id and 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.

Does the above code invoke this specific undefined behaviour? Could a high-quality implementation be expected to report a warning?


Solution

  • Does the above code invoke this specific undefined behaviour?

    Yes. [temp.point]/7:

    The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

    The point of instantiation is right after the first static_assert-declaration:

    For […] a specialization for a […] static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization […]. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.

    So there would indeed be a better match if we would have considered the second function declaration - which we couldn't, since it's declared after the point of instantiation of A<D>::value. According to the rule you quoted the code induces undefined behavior.
    The rule is basically extending the ODR to dependent name lookup in templates.

    Could a high-quality implementation be expected to report a warning?

    I wouldn't. Consider also that undefined behavior reaches back to compile time; The compiler is allowed but not required to issue a warning or error message if the code induces UB. Do not expect compilers to always point out illegal code.