Когда 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).