Dependent name and scope

Viewed 118

Does anyone know what is going on with the compiler and/or language standard when certain dependent functions are in scope after vs before use? I'm using a close copy of Stroustrup's example in C++ 4th Ed Page 747. In the DEP_NAME example g and Q can be declared after the template function f and are in scope, however in the non DEP_NAME example the opposite is true.

Appreciate your guidance!

#define DEP_NAME
#ifdef DEP_NAME
template<typename T> T f(T a)
{
    return g(a); // OK: a is a dependent name and therefore so is g
}
// can be declared after f
class Q { };
Q g(Q e)
{
    return e;
}
#else
// must be declared before f
class Q { };
Q g(Q e)
{
    return e;
}
template<typename T> T f(T a)
{
    return g(Q{});
}
#endif

int main(int argc, char *argv[])
{
    Q z = f(Q{});
    return 0;
}
1 Answers

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


Postponed lookup of dependent names allows for ADL to find names declared after the function template definition

All non-dependent names in a function template definition must be declared at the point of the definition. Dependent names need only be declared at the time of the first instantiation, for a given specialization, if they can be found via argument-dependent lookup (ADL) for the particular instantiation. In your first example, g is a dependent name as it depends on the type template parameter, and it can moreover be found via ADL on ::Q, and thus the first example is well-formed. In your second example g is not a dependent name as it does not depend on the type template parameter, and as the name is moreover not visible at the point of the function template definition, the second example is ill-formed.

We can summarize the rules quoted from the relevant standard passages (see Section Details) as:

  • Lookup for non-dependent names follows the usual lookup rules, and thus the name need to be declared at the point of the function template definition,
  • Lookup for dependent names is postponed until a given instantiation. However, at the time of this instantiation, only argument-dependent lookup (ADL) will be able to find names that were not declared at the point of the function template definition, but were declared at the point of the given instantiation.

The rules of latter bullet means that the following example is well-formed:

namespace ns {

template<typename T> T f(T a) {
    // Can find ::ns::g(Q) only via ADL on T for
    // an instantiation of f with T == ::ns::Q.
    return g(a);
}

class Q {};

Q g(Q e) { return e; }

}  // namespace ns

int main() {
    (void) f(ns::Q{});
    return 0;
}

as is the following example:

template<typename T> T f(T a) {
    // Can find ::ns::g(Q) only via ADL on T for
    // an instantiation of f with T == ::ns::Q.
    return g(a);
}

namespace ns {

class Q {};

Q g(Q e) { return e; }

}  // namespace ns

int main() {
    (void) f(::ns::Q{});
    return 0;
}

whereas the following example is ill-formed, as ADL cannot find g for an instantiation of ::ns::Q:

template<typename T> T f(T a) {
    // Cannot find ::g(Q) as ADL on T for
    // an instantiation of f with T == ::ns::Q
    // will only consider the ::ns namespace.
    return g(a);
}

namespace ns {

class Q {};
    
}  // namespace ns  

::ns::Q g(::ns::Q e) { return e; }

int main() {
    (void) f(::ns::Q{});
    return 0;
}

For the latter, the compiler (in this case, Clang) even presents an instructive error message as for why the program is ill-formed:

error: call to function 'g' that is neither visible in the template
       definition nor found by argument-dependent lookup

Details

[temp.res]/9 states [extract, emphasis mine]:

[temp.res]/9 When looking for the declaration of a name used in a template definition, the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known ([temp.dep]). [ Example: ... ] [...]

[temp.dep.res]/1 is clear that only declarations that are visible at the point of definition of the template are considered for non-qualified (dependent) name lookup [emphasis mine]:

[temp.dep.res]/1 In resolving dependent names, names from the following sources are considered:

  • (1.1) Declarations that are visible at the point of definition of the template.
  • (1.2) Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point]) and from the definition context.

a fact that is repeated in [temp.dep.candidate]/1 [emphasis mine]:

[temp.dep.candidate]/1 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:

  • (1.1) For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
  • (1.2) 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.
Related