Will C++ argument dependent lookup kick in for lookup of variable and type name?

129 Views Asked by At

Consider the following C++ program

template<class T>
struct C;

template<class T>
struct B{
    void f(){
        cout << "a=" << C<T>::a << endl;
    }
};

template<class T>
struct C{static const int a = 1;};

int main()
{
    B<int> b;
    b.f();
    return 0;
}

g++ and clang++ think it as a right C++ program. Ok, C<T>::a should be a dependent name and compiler will look up it in instantiation(i.e., when b.f() is called).

What puzzles me is that cppreference says ADL is used for function call expression, so if here ADL does not kick in, how compiler find the declaration of a?

I have tried the following program and it shows ordinary lookup in definition can't find the declaration of a?

#include <iostream>
using namespace std;

class D;

struct E{
    void f(){
         cout << D::a << endl; // error: incomplete type ‘D’ used in nested name specifier
    }
};

class D{
    static const int a = 7;
};
 
int main()
{
    E e;
    e.f();
    return 0;
}
1

There are 1 best solutions below

0
ckf104 On

First, after reading relevant passage in Standard that Igor Tandetnik quoted. I think it can't be used to explain my question.

[temp.dep.type]/8 said

If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context. If the result of this lookup differs from the result of name lookup in the template definition context, name lookup is ambiguous.

Standard also gives a example

struct A {
int m;
};
struct B {
int m;
};
template<typename T>
struct C : A, T {
int f() { return this->m; } // finds A::m in the template definition context
int g() { return m; } // finds A::m in the template definition context
};
template int C<B>::f(); // error: finds both A::m and B::m
template int C<B>::g(); // OK: transformation to class member access syntax
                        // does not occur in the template definition context; see 11.4.2

Here m and this->m in f() and g() refers to a member of the current instantiation C<T>::A::m(because A is a non-dependent base class).

So, based on this rule, this->m will be looked up again in template instantiation context and find B::m, error raises. But m in g() is not a qualified-id or class member access expression, so template int C<B>::g(); is safe.

Back to my example that puzzles me, C<T>::a is actually a member of an unknown specialization. [temp.dep.type]/6:

A name is a member of an unknown specialization if it is a qualified-id in which the nested-name-specifier names a dependent type that is not the current instantiation.

The awkward thing is that I only find the lookup rules for function name in the standard. I'd like to think compiler just do a usual lookup for dependent name which is not a function name in template instantiation context.

About point of instantiation, [temp.point]/4 say

For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

So, point of instantiation of template struct C is before template struct B and C<T>::a can be accessed.

But intuitive explanation "The name a gets resolved when B<T>::f() gets instantiated" of NathanOliver may be enough for daily programming.