在下面的代码中,f()
函数可以为不完整的operator bool()
调用operator *()
和unique_ptr<C>
成员函数class C
。但是,当函数g()
尝试为unique_ptr<X<C>>
调用那些相同的成员函数时,编译器突然想要一个完整的类型并尝试实例化X<C>
,然后失败。出于某种原因,unique_ptr<X<C>>::get()
不会导致模板实例化并正确编译,如函数h()
中所示。这是为什么?是什么让get()
与operator 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 {};
答案 0 :(得分:7)
这是一个人为的简化示例,仅使用我们自己的类型:
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>
可以声明需要调用的friend void foo(Ptr<Wrap<Incomplete >> )
。或者Wrap<Incomplete>
可能具有依赖基础,其名称空间也需要考虑。此时的实例化会使代码格式错误,因为Incomplete
是一个不完整的类型,并且您不能拥有不完整类型的成员。
回到原始问题,对!pX
和*pX
的调用会调用ADL,从而导致X<C>
即时形成的格式错误。对pX.get()
的调用不调用ADL,这就是为什么一个正常工作的原因。
有关详细信息,请参阅this answer,CWG 557。
答案 1 :(得分:5)
不是需要完整类型的unique_ptr
,而是您的班级X
。
std::unique_ptr<C> pC;
您实际上还没有为C
进行任何分配,因此编译器不需要在此处了解C
的具体内容。
std::unique_ptr<X<C>> pX;
在此,您使用C
作为X
的模板类型。由于X
包含类型为T
的对象,C
,因此编译器需要知道在X
实例化时要分配的内容。 (t
是一个对象,因此在构造时实例化)。将T t;
更改为T* t;
,编译器就不会抱怨。
修改强>
这并不能解释为什么h()编译,但g()不编译。
This example编译良好:
#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;
typename std::add_lvalue_reference<X<C>>::type DoSomeStuff() // exact copy of operator*
{
return (*pX.get());
}
void g() {
if ((bool)pX) return;
}
class C {};
int main()
{
auto z = DoSomeStuff();
}
这使得它更有趣,因为它模仿operator*
但做编译。从表达式中删除!
也有效。 这似乎是多个实现中的错误(MSVC,GCC,Clang)。