Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behavior when perform argument dependent name lookup in template

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.

like image 485
sunlight07 Avatar asked Oct 20 '22 17:10

sunlight07


1 Answers

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();
}
like image 84
dyp Avatar answered Nov 04 '22 01:11

dyp