Skip to content

关于本章

对于template,C++ compiler有着特殊的处理,这是本章讨论的问题。

thegreenplace Dependent name lookup for C++ templates

1、参见 thegreenplace-Dependent-name-lookup-for-C++templates 章节。

2、这篇文章非常好,说明清楚了:

a、为什么需要dependent name lookup

b、如何使用

TO READ

cppreference Two-phase name lookup

cppreference Dependent names

Inside the definition of a template (both class template and function template), the meaning of some constructs may differ from one instantiation to another. In particular, types and expressions may depend on types of type template parameters and values of non-type template parameters.

NOTE: 下面是对最后一句话的理解:

template parameter
type template parameters type
non-type template parameters value
template<typename T>
struct X : B<T> // "B<T>" is dependent on T
{
    typename T::A* pa; // "T::A" is dependent on T
                       // (see below for the meaning of this use of "typename")
    void f(B<T>* pb) {
        static int i = B<T>::i; // "B<T>::i" is dependent on T
        pb->j++; // "pb->j" is dependent on T
    }
};

Name lookup and binding are different for dependent names and non-dependent names

Binding rules

Non-dependent names are looked up and bound at the point of template definition. This binding holds even if at the point of template instantiation there is a better match:

#include <iostream>
void g(double)
{
    std::cout << "g(double)\n";
}

template<class T>
struct S
{
    void f() const
    {
        g(1); // "g" is a non-dependent name, bound now
    }
};

void g(int)
{
    std::cout << "g(int)\n";
}

int main()
{
    g(1); // calls g(int)

    S<int> s;
    s.f(); // calls g(double)
}
// g++ test.cpp

NOTE: 上述程序的输出如下:

g(int)
g(double)

可以看到s.f(); // calls g(double),即使有更好的overload void g(int)

Binding of dependent names is postponed until lookup takes place.

Lookup rules

As discussed in lookup, the lookup of a dependent name used in a template is postponed until the template arguments are known, at which time

1、non-ADL lookup examines function declarations with external linkage that are visible from the template definition context

2、ADL examines function declarations with external linkage that are visible from either the template definition context or the template instantiation context

(in other words, adding a new function declaration after template definition does not make it visible, except via ADL).

NOTE: 综合上面描述的compiler进行lookup的两个规则,可知:

template definition context template instantiation context
non-ADL lookup yes no
ADL lookup yes yes

从中可以看出,在*template instantiation* context是不执行non-ADL lookup,后面会对此进行详细解释,尤其需要注意的是搞清楚这样做的原因。

(in other words, adding a new function declaration after template definition does not make it visible, except via ADL).

The purpose of this rule is to help guard against violations of the ODR for template instantiations:

#include <iostream>
#include <vector>

// an external library
namespace E
{
template<typename T>
void writeObject(const T &t)
{
    std::cout << "Value = " << t << '\n';
}
}

// translation unit 1:
// Programmer 1 wants to allow E::writeObject to work with vector<int>
namespace P1
{
std::ostream& operator<<(std::ostream &os, const std::vector<int> &v)
{
    for (int n : v)
        os << n << ' ';
    return os;
}
void doSomething()
{
    std::vector<int> v;
    E::writeObject(v); // error: will not find P1::operator<<
}
}

// translation unit 2:
// Programmer 2 wants to allow E::writeObject to work with vector<int>
namespace P2
{
std::ostream& operator<<(std::ostream &os, const std::vector<int> &v)
{
    for (int n : v)
        os << n << ':';
    return os << "[]";
}
void doSomethingElse()
{
    std::vector<int> v;
    E::writeObject(v); // error: will not find P2::operator<<
}
}
int main()
{
    P1::doSomething();
    P2::doSomethingElse();
}
// g++ --std=c++11 test.cpp

NOTE: 上述程序编译报错,报错如下:

test.cpp: In instantiation of void E::writeObject(const T&) [with T = std::vector<int>]:
test.cpp:27:18:   required from here
test.cpp:10:26: 错误:无法将左值‘std::basic_ostream<char>’绑定到‘std::basic_ostream<char>&&
  std::cout << "Value = " << t << '\n';

这是因为compiler无法找到用户重载的std::ostream& operator<<(std::ostream &os, const std::vector<int> &v),原因如下:

  • 通过前面描述的规则可知,non-ADL lookup for operator<< were allowed from the instantiation context,所以无法找到namespace P1中的std::ostream& operator<<(std::ostream &os, const std::vector<int> &v)
  • std::vector<int>不是user-defined type,因此不会触发ADL,所以无法找到在namespace P1中提供的上述重载。

下面是另外一个example,它 通过引入user-define type来触发ADL,从而使程序能够编译通过。

In the above example, if non-ADL lookup for operator<< were allowed from the instantiation context, the instantiation of E::writeObject<vector<int>> would have two different definitions: one using P1::operator<< and one using P2::operator<<. Such ODR violation may not be detected by the linker, leading to one or the other being used in both instances.

To make ADL examine a user-defined namespace, either std::vector should be replaced by a user-defined class or its element type should be a user-defined class:

#include <iostream>
#include <vector>

// an external library
namespace E
{
template<typename T>
void writeObject(const T &t)
{
    std::cout << "Value = " << t << '\n';
}
}

// translation unit 1:
// Programmer 1 wants to allow E::writeObject to work with vector<int>
namespace P1
{
class C
{

};
std::ostream& operator<<(std::ostream &os, const C &v)
{
    os << "class P1::C";
    return os;
}
std::ostream& operator<<(std::ostream &os, const std::vector<C> &v)
{
    for (C n : v)
        os << n << ' ';
    return os;
}
void doSomething()
{
    std::vector<C> v;
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();
    E::writeObject(v); // error: will not find P1::operator<<
}
}

// translation unit 2:
// Programmer 2 wants to allow E::writeObject to work with vector<int>
namespace P2
{
class C
{

};
std::ostream& operator<<(std::ostream &os, const C &v)
{
    os << "class P2::C";
    return os;
}
std::ostream& operator<<(std::ostream &os, const std::vector<C> &v)
{
    for (C n : v)
        os << n << ' ';
    return os;
}
void doSomethingElse()
{
    std::vector<C> v;
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();
    E::writeObject(v); // error: will not find P2::operator<<
}
}

int main()
{
    P1::doSomething();
    P2::doSomethingElse();
}
// g++ --std=c++11 test.cpp

Note: this rule makes it impractical to overload operators for standard library types

NOTE:

#include <iostream>
#include <vector>
#include <iterator>
#include <utility>
// Bad idea: operator in global namespace, but its arguments are in std::
std::ostream& operator<<(std::ostream &os, std::pair<int, double> p)
{
    return os << p.first << ',' << p.second;
}

int main()
{
    typedef std::pair<int, double> elem_t;
    std::vector<elem_t> v(10);
    std::cout << v[0] << '\n'; // OK, ordinary lookup finds ::operator<<
    std::copy(v.begin(), v.end(), std::ostream_iterator < elem_t > (std::cout, " ")); // Error: both ordinary
    // lookup from the point of definition of std::ostream_iterator and ADL will
    // only consider the std namespace, and will find many overloads of
    // std::operator<<, so the lookup will be done. Overload resolution will then
    // fail to find operator<< for elem_t in the set found by the lookup.
}

NOTE: 上述程序报错如下:

/usr/include/c++/4.8.2/bits/stl_algobase.h:428:38:   required from _OI std::__copy_move_a2(_II, _II, _OI) [with bool _IsMove = false; _II = __gnu_cxx::__normal_iterator<std::pair<int, double>*, std::vector<std::pair<int, double> > >; _OI = std::ostream_iterator<std::pair<int, double> >]
/usr/include/c++/4.8.2/bits/stl_algobase.h:460:17:   required from _OI std::copy(_II, _II, _OI) [with _II = __gnu_cxx::__normal_iterator<std::pair<int, double>*, std::vector<std::pair<int, double> > >; _OI = std::ostream_iterator<std::pair<int, double> >]
test.cpp:16:81:   required from here
/usr/include/c++/4.8.2/bits/stream_iterator.h:198:13: 错误:无法将左值‘std::ostream_iterator<std::pair<int, double> >::ostream_type {aka std::basic_ostream<char>}’绑定到‘std::basic_ostream<char>&&
  *_M_stream << __value;
             ^
In file included from /usr/include/c++/4.8.2/iostream:39:0,
                 from test.cpp:1:
/usr/include/c++/4.8.2/ostream:602:5: 错误:以初始化‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = std::pair<int, double>]’的实参 1
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)

错误的原因在于,compiler的name lookup机制无法找到定义在global namespace的std::ostream& operator<<(std::ostream &os, std::pair<int, double> p);正如源程序中所注释的:

Bad idea: operator in global namespace, but its arguments are in std::

#include <iostream>
#include <vector>
#include <iterator>
#include <utility>
namespace std
{
// Bad idea: operator in global namespace, but its arguments are in std::
std::ostream& operator<<(std::ostream &os, std::pair<int, double> p)
{
    return os << p.first << ',' << p.second;
}
}

int main()
{
    typedef std::pair<int, double> elem_t;
    std::vector<elem_t> v(10);
    std::cout << v[0] << '\n'; // OK, ordinary lookup finds ::operator<<
    std::copy(v.begin(), v.end(), std::ostream_iterator < elem_t > (std::cout, " ")); // Error: both ordinary
    // lookup from the point of definition of std::ostream_iterator and ADL will
    // only consider the std namespace, and will find many overloads of
    // std::operator<<, so the lookup will be done. Overload resolution will then
    // fail to find operator<< for elem_t in the set found by the lookup.
}

Dependent types

The following types are dependent types:

dependent type explanation 说明
template parameter 这是最最常见的dependent type

Note: a typedef member of a current instantiation is only dependent when the type it refers to is.

NOTE: 这句话要如何理解?

Type-dependent expressions

The following expressions are type-dependent

NOTE: 下面收录了我目前遇到过的:

1) this, if the class is a dependent type.

NOTE: 参见C++\Language-reference\Classes\Members\Non-static-member\this-pointer章节。

Value-dependent expressions

Current instantiation

Within a class template definition (including its member functions and nested classes) some names may be deduced to refer to the current instantiation. This allows certain errors to be detected at the point of definition, rather than instantiation, and removes the requirement on the typename and template disambiguators for dependent names, see below.

#include <iostream>
#include <vector>

template<class T> class A
{
    A *p1;    // A is the current instantiation
    A<T> *p2; // A<T> is the current instantiation
    ::A<T> *p4; // ::A<T> is the current instantiation
    A<T*> p3; // A<T*> is not the current instantiation
    class B
    {
        B *p1; // B is the current instantiation
        A<T>::B *p2; // A<T>::B is the current instantiation
        typename A<T*>::B *p3; // A<T*>::B is not the current instantiation
    };
};
template<class T> class A<T*>
{
    A<T*> *p1;  // A<T*> is the current instantiation
    A<T> *p2;   // A<T> is not the current instantiation
};
template<int I> struct B
{
    static const int my_I = I;
    static const int my_I2 = I + 0;
    static const int my_I3 = my_I;
    B<my_I> *b3;  // B<my_I> is the current instantiation
    B<my_I2> *b4; // B<my_I2> is not the current instantiation
    B<my_I3> *b5; // B<my_I3> is the current instantiation
};

int main()
{
    B<5> b;

    A<int> a1;
    A<int*> a2;
}
// g++ --std=c++11 test.cpp

The typename disambiguator for dependent names

#include <iostream>
#include <vector>

int p = 1;
template<typename T>
void foo(const std::vector<T> &v)
{

    // std::vector<T>::const_iterator is a dependent name,
    typename std::vector<T>::const_iterator it = v.begin();

    // without 'typename', the following is parsed as multiplication
    // of the type-dependent member variable 'const_iterator'
    // and some variable 'p'. Since there is a global 'p' visible
    // at this point, this template definition compiles.
    // std::vector<T>::const_iterator *p;

    typedef typename std::vector<T>::const_iterator iter_t;
    iter_t *p2; // iter_t is a dependent name, but it's known to be a type name
}

template<typename T>
struct S
{
    typedef int value_t; // member of current instantiation
    void f()
    {
        S<T>::value_t n { };  // S<T> is dependent, but 'typename' not needed
        std::cout << n << '\n';
    }
};

int main()
{
    std::vector<int> v;
    foo(v); // template instantiation fails: there is no member variable
            // called 'const_iterator' in the type std::vector<int>
    S<int>().f();
}
// g++ --std=c++11 test.cpp

The template disambiguator for dependent names

NOTE:

1、这部分内容移到了 The-template-disambiguator-for-dependent-names 章节

TO READ