5

Recently I was studying the exact meaning of the well-known "two-phase name lookup" for the names in template classes. Although I have read a lot of articles about this, I still cannot know everything about this. Now I was confusing about the code shown below:

template<typename T>
class A
{
public:
    void f(T, T){};
};

namespace ns
{
    typedef int TT;
    void f(int, int){};
};

template<typename T>
class B : public A<T>
{
public:
    void g()
    {

        //f(T(), T()); // it's fine for error here
        typedef ns::TT TTT;
        f(TTT(), T()); // why this issued an error?
        f(ns::TT(), T()); // and this?
    }
};

/* I also think it's OK to move ns here */
// namespace ns
// {
//  typedef int TT;
//  void f(int, int){};
//};

int main()
{
    B<int> b;
    b.g();
}

Please notice the second comment. Since "f" is a dependent name, its lookup should be delayed until the instantiation in the "main" function. And at that time, the compiler should perform an argument dependent name lookup at the scope of the main function. I think now it should discover the function in namespace ns, but it still issued a compile error:

1.cpp: In instantiation of 'void B<T>::g() [with T = int]':
1.cpp:30:6:   required from here
1.cpp:23:15: error: 'f' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]    f(TTT(), T()); //why this issued an error?
               ^
1.cpp:23:15: note: declarations in dependent base 'A<int>' are not found by unqualified lookup
1.cpp:23:15: note: use 'this->f' instead

Could someone explain this to me? Thanks.

sunlight07
  • 686
  • 1
  • 7
  • 21
  • 2
    [basic.lookup.argdep]/2 "For each argument type `T` in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). **Typedef names and using-declarations used to specify the types do not contribute to this set.**" [emphasis mine] I don't think this issue is related to templates. – dyp Jan 04 '14 at 18:05
  • So what about the error occurred at the third comment? f(ns::TT(), T())? – sunlight07 Jan 04 '14 at 18:08
  • Well `ns::TT` is a typedef for an `int`. And `ns` is not an associated namespace of `int`. The same rule applies. (The presence of `ns::` just changes name lookup for the `TT` following the `::`, it doesn't alter name lookup of `f` - that's still only dependent on the types of the arguments.) – dyp Jan 04 '14 at 18:13
  • Ah - wait, I somehow overlooked the inheritance. The base class scope is not searched during unqualified lookup as the base class `A` is dependent. – dyp Jan 04 '14 at 18:15
  • @DyP, OK, thanks! So.. furthermore, could you show me an example where the argument dependent name lookup takes effect in similar situations? – sunlight07 Jan 04 '14 at 18:19
  • @DyP, for example, I add an int variable "tt" in namespace ns and use "f(ns::tt, T())".. same error.. – sunlight07 Jan 04 '14 at 18:21

1 Answers1

3

Argument-dependent lookup only searches the associated classes and namespaces of the argument type. A typedef is just a transparent alias, similarly a using-declaration.

From the draft Standard n3485, [basic.lookup.argdep]/2 about argument-dependent lookup:

For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set.

[emphasis mine]


template<typename T>
class A
{
public:
    void f(T, T){};
};

namespace ns
{
    typedef int TT;
    void f(int, int){};
};

template<typename T>
class B : public A<T> // note the base class is dependent
{
public:
    void g()
    {
        //f(T(), T()); // it's fine for error here
        typedef ns::TT TTT;
        f(TTT(), T()); // why this issued an error?
        f(ns::TT(), T()); // and this?
    }
};

As the base class is dependent, it won't be searched during unqualified lookup. Therefore, f can be found using argument-dependent lookup. You correctly stated that f will be searched for only during the "second phase" (at the point of instantiation), as in your invocations, at least one argument is dependent on a template-parameter.

However, ns is not a dependent name, as well as TT (in ns::TT). Therefore, the namespace and TT must be declared before they're used in the definition of g.

Whether you write f(ns::TT(), T()) or f(T(), T()) does not influence the general rule where f is searched during argument-dependent lookup: Only the associated namespaces and classes of the types of the arguments (of T and ns::TT). Both are ints for B<int>::g(), so there are no associated classes and namespaces.

f(TTT(), TTT()) changes lookup insofar as f now is lookup up in the first name lookup phase (it's not a dependent name).


Here's an example of argument-dependent lookup:

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

int main()
{
    ns::TT x;
    f(x, x);
}

Now, you can do this as well inside a member function of a class template:

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

template<typename T>
struct B
{
    void g()
    {
        f(T(), T());
    }
};

int main()
{
    B<ns::TT> x;
    x.g();
}

But, as I said, argument-dependent name lookup doesn't work for fundamental types such as int.

In the example above, f is again dependent, as at least one of the arguments is dependent on a template-parameter. Therefore, you could write as well:

template<typename T>
struct B
{
    void g()
    {
        f(T(), T()); // looked up during the second phase,
                     // from the point of instantiation
    }
};

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

int main()
{
    B<ns::TT> x;
    x.g();
}
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Thanks for your explanation! One more question, if I split the namespace ns into two parts: struct TT is defined before main function and f is defined after main function. No error occurs. Is it correct? If I add "ns::TT y; f(y, y);" after x.g() in the main function and move the declaration, it says "f is not declared"? But for template it's OK? – sunlight07 Jan 04 '14 at 18:47
  • @sunlight07 That's the difference between lookup of dependent names inside templates and names outside templates: `f` in `f(T(), T())` is a dependent name and lookup up "during the second phase", from the point of instantiation. Actually, there are several such points, and one is at the end of the file. Therefore, a declaration of `f` can be found that's after the definition of `g` and even after the invocation of `g` in `main`. However, IIRC you need to be careful with that (declaration after invocation). – dyp Jan 04 '14 at 18:52
  • As a practical matter (weird that nobody's mentioned this), a local `using ns::f;` would do wonders, I think. – Cheers and hth. - Alf Jan 04 '14 at 18:59
  • @DyP er...I was confused.. I don't quite understand "several such points" especially the one at the end of the file. In the gcc changelog http://gcc.gnu.org/gcc-4.7/changes.html, there are two examples about the two phase name lookup. I think they may not match your explanation? – sunlight07 Jan 04 '14 at 19:00
  • @Cheersandhth.-Alf Yes, but it wouldn't be ADL, then ;) – dyp Jan 04 '14 at 19:00
  • @sunlight07 IMHO, unqualified lookup of dependent function names isn't really described in the Standard. It only says that in an expression of the form *postfix-expression* `(` *expression-list* `)`, if the *postfix-expression* is an *unqualified-id* (a name w/o any `::`), then "Such names are unbound and are looked up at the point of the template instantiation in both the context of the template definition and the context of the point of instantiation." It seems as this is interpreted such that it only applies to ADL, but not to unqualified lookup (which then only happens in phase 1). – dyp Jan 04 '14 at 19:16
  • @DyP: Specializations visible at point of use but not at the point of the template definition definitely will be found. Ditto for destructors of types complete at point of use but not at the template definition. – Ben Voigt Jan 04 '14 at 19:23