Когда unique_ptr требует полного типа?
В приведенном ниже коде функция 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 {};
Ответы
Ответ 1
Вот придуманный и упрощенный пример, используя только наши собственные типы:
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, поэтому он отлично работает.
Подробнее см. этот ответ, а также CWG 557.
Ответ 2
Это не 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;
, и компилятор не будет жаловаться.
Edit:
Это не объясняет, почему компиляция h() компилируется, но g() не делает.
Этот пример компилируется отлично:
#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).