当前位置:网站首页>Core knowledge of C + + 11-17 template (13) -- name search and ADL

Core knowledge of C + + 11-17 template (13) -- name search and ADL

2020-12-06 00:17:53 Zhang Yachen

stay C++ in , If the compiler encounters a name , It looks for what the name stands for . such as x*y, If x and y Is the name of the variable , So it's multiplication . If x It's the name of a type , Then a pointer is declared .

C++ It's a context-sensitive Language : You have to know the context to know the meaning of the expression . So what is the relationship between this and the template ? To construct a template, you must know several contexts :

  • The context in which the template appears
  • The context in which the template is instantiated
  • Context of instantiating template parameters

Name classification

Introduce two important concepts :

  • qualified name : The scope to which a name belongs Be explicitly specified , for example ::-> perhaps ..this->count It's just one. qualified name, but count No , Because its scope is not indicated by the display , Even if it's with this->count It is equivalent. .
  • dependent name: Depends on template parameters . for example :std::vector<T>::iterator. But if T Is an alias of a known type (using T = int), Then it's not dependent name.

image

There's a lot of detail in the name search , Here we focus only on a few main points .

ordinary lookup

about qualified name Come on , It will show the specified scope . If the scope is a class , Then base classes are also taken into account , But the outer scope of a class is not considered :

int x;

class B {
public:
  int i;
};

class D : public B {};

void f(D *pd) {
  pd->i = 3;    // finds B::i
  D::x = 2;      // ERROR: does not find ::x in the enclosing scope
}

This is very intuitive .

contrary , For non qualified name Come on , It will look up layer by layer in the peripheral scope ( If in a class member function , The scope of this class and base class will be found first ). It's called ordinary lookup :

extern int count;             // #1

int lookup_example(int count) // #2
{
  if (count < 0) {
    int count = 1;           // #3
    lookup_example(count);   // unqualified count refers to #3
  }
  return count + ::count;      // the first (unqualified) count refers to #2 ;
}                              // the second (qualified) count refers to #1

This example is also intuitive .

But the following example is not so normal :

template<typename T>
T max (T a, T b) {
    return b < a ? a : b;
}

namespace BigMath {
  class BigNumber {
    ...
};

  bool operator < (BigNumber const&, BigNumber const&);
  ...
}

using BigMath::BigNumber;

void g (BigNumber const& a, BigNumber const& b) {
  ...
  BigNumber x = ::max(a,b);
  ...
}

The problem here is : When calling max when ,ordinary lookup It won't be found BigNumber Of operator <. If there are no special rules , So in C++ namespace Scene , Will greatly limit the adaptability of the template .ADL That's the special rule , To solve this kind of problem .

ADL (Argument-Dependent Lookup)

ADL Appear in the C++98/C++03 in , It's also called Koenig lookup, Apply to non qualified name On ( Hereinafter referred to as" unqualified name). stay Function call expression in (f(a1, a2, a3, ... ), Contains implicit call overloading operator, for example << ),ADL Apply a set of rules to find unqualified function names.

ADL Will be the function expression in the actual parameter of associated namespaces and associated classes Add to the search scope , That's why it's called Argument-Dependent Lookup. for example : A certain type refers to class X The pointer to , So it's associated namespaces and associated classes Will contain X and X Any of the following class and namespace.

For a given type ,associated classes and associated namespaces Define according to certain rules , You can have a look at Official website Argument-dependent lookup, It's a little bit more , It's not written here . Understand why ADL、 When will it be applied to ADL when , According to the corresponding scene to check on the line ~

An additional point to note is ,ADL Will ignore using :

#include <iostream>

namespace X {
  template <typename T> void f(T);
}

namespace N {
  using namespace X;
  enum E { e1 };
  void f(E) { std::cout << "N::f(N::E) called\n"; }
}    // namespace N

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

int main() {
  ::f(N::e1);    // qualified function name: no ADL
  f(N::e1);     // ordinary lookup finds ::f() and ADL finds N::f(), the latter is preferred
}      

namespace N Medium using namespace X Will be ADL Ignore , So in main Function ,X::f() It won't be considered .

Examples of the official website

look down Official website Examples of how to understand :

#include <iostream>
int main() {
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // same, using function call notation
 
    // however,
    std::cout << endl; // Error: 'endl' is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
 
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
 
    (endl)(std::cout); // Error: 'endl' is not declared in this namespace.
                       // The sub-expression (endl) is not a function call expression
}

Pay attention to the last point (endl)(std::cout);, If the name of the function is enclosed in parentheses , That doesn't apply either ADL.

One more :

namespace A {
      struct X;
      struct Y;
      void f(int);
      void g(X);
}
 
namespace B {
    void f(int i) {
        f(i);      // calls B::f (endless recursion)
    }
    void g(A::X x) {
        g(x);   // Error: ambiguous between B::g (ordinary lookup)
                //        and A::g (argument-dependent lookup)
    }
    void h(A::Y y) {
        h(y);   // calls B::h (endless recursion): ADL examines the A namespace
                // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

This is easier to understand , No explanation. .

ADL The shortcomings of

rely on ADL It may lead to semantic problems , That's why sometimes it's necessary to add ::, Or it is generally recommended to use xxx::func, instead of using namespace xxx . Because the former is qualified name, No, ADL The process of .

quote modern C++ And ADL Examples in , Just look at swap Just go , Other functions of the class can be omitted :


#include <iostream>
 
namespace A {
    template<typename T>
    class smart_ptr {
    public:
        smart_ptr() noexcept : ptr_(nullptr) {
 
        }
 
        smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) {
 
        }
 
        smart_ptr(smart_ptr &rhs) noexcept {
            ptr_ = rhs.release();       //  Release ownership , here rhs Of ptr_ Pointer for nullptr
        }
 
        smart_ptr &operator=(smart_ptr rhs) noexcept {
            swap(rhs);
            return *this;
        }
 
        void swap(smart_ptr &rhs) noexcept { // noexcept == throw()  Make sure you don't throw an exception 
            using std::swap;
            swap(ptr_, rhs.ptr_);
        }
 
        T *release() noexcept {
            T *ptr = ptr_;
            ptr_ = nullptr;
            return ptr;
        }
 
        T *get() const noexcept {
            return ptr_;
        }
 
    private:
        T *ptr_;
    };
 
//  Provide a non member swap function for ADL(Argument Dependent Lookup)
    template<typename T>
    void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
        lhs.swap(rhs);
    }
}
 
//  Open this comment , May trigger ADL Conflict 
//namespace std {
//    //  Provide a non member swap function for ADL(Argument Dependent Lookup)
//    template<typename T>
//    void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
//        lhs.swap(rhs);
//    }
//
//}
 
int main() {
 
    using std::swap;
    A::smart_ptr<std::string> s1("hello"), s2("world");
    //  Exchange before 
    std::cout << *s1.get() << " " << *s2.get() << std::endl;
    swap(s1, s2);      //  here swap  Can pass Koenig Search or ADL according to s1 And s2 To find swap function 
    //  After exchanging 
    std::cout << *s1.get() << " " << *s2.get() << std::endl;
}

( End )

Friends can pay attention to my official account , Get the most up-to-date updates :

image

版权声明
本文为[Zhang Yachen]所创,转载请带上原文链接,感谢
https://chowdera.com/2020/12/20201206001545879v.html