在下面的代码中,函数f()可以调用运算符bool()和operator *()的成员函数unique_ptr< C>对于不完整的类C,但是当函数g()尝试为unique_ptr< X>>调用那些相同的成员函数时,编译器突然想要一个完整的类型并尝试实例化X C,然后X C失败.由于某些原因,unique_ptr< X> :: get()不会导致模板实例化并正确编译,如在函数h()中可以看到的那样.这是为什么?什么使get()与运算符bool()和operator *()不同?
#include <memory> class C; std::unique_ptr<C> pC; C& f() { if ( !pC ) throw 0; // OK,even though C is incomplete return *pC; // OK,even though C is incomplete } template <class T> class X { T t; }; std::unique_ptr<X<C>> pX; X<C>& g() { if ( !pX ) throw 0; // Error: 'X<C>::t' uses undefined class 'C' return *pX; // Error: 'X<C>::t' uses undefined class 'C' } X<C>& h() { if ( !pX.get() ) throw 0; // OK return *pX.get(); // OK } class C {};
解决方法
这里是一个设计简单的例子,仅使用我们自己的类型:
class Incomplete; template <class T> struct Wrap { T t; }; template <class T> struct Ptr { T* p; void foo() { } }; template <class T> void foo(Ptr<T> ) { } int main() { Ptr<Incomplete>{}.foo(); // OK foo(Ptr<Incomplete>{}); // OK Ptr<Wrap<Incomplete>>{}.foo(); // OK ::foo(Ptr<Wrap<Incomplete>>{}); // OK! foo(Ptr<Wrap<Incomplete>>{}); // error }
问题是,当我们对foo进行不合格的调用时,而不是对:: foo进行限定的调用,或者调用成员函数Ptr< T> :: foo(),我们触发参数相关的查找.
ADL将在类模板特殊化中查找模板类型的关联命名空间,这将触发隐式模板实例化.需要触发模板实例化以便执行ADL查找,因为例如Wrap< Incomplete>可以声明一个需要调用的朋友void foo(Ptr< Wrap< Incomplete>>).或包裹<不完整>可能有依赖基地,其名称空间也需要考虑.此时的实例化使得代码不正确,因为Incomplete是一个不完整的类型,并且您不能拥有不完整类型的成员.
回到原来的问题,对!pX和* pX的调用调用ADL,这导致X C的实例化.这是不正式的.对pX.get()的调用不会调用ADL,这就是为什么一个工作正常.
更多细节见this answer,也是CWG 557.