Почему этот конструктор не дает ошибку неполного типа?

У меня есть два файла test.h и main.cpp, как показано ниже:

test.h

#include <memory>

class TestImpl;

template <typename... T>
void createConnection(T&&... Args)
{
    // 1. Why is this working if the constructor is in cpp?
    std::unique_ptr<TestImpl> pimpl(new TestImpl(std::forward<T>(Args)...));
    std::cout << "Done..." << std::endl;

    // 2. Why is this not working if the constructor call has no issues?
    pimpl->sayHello();
}

main.cpp

#include <iostream>

#include "test.h"

class TestImpl
{
public:
    TestImpl(const std::string& first, const std::string& second)
        : _first(first)
        , _second(second)
    {
    }

    void sayHello()
    {
        std::cout << "Hello ... " << std::endl;
    }

private:
    std::string _first;
    std::string _second;
};

int main()
{
    std::cout << "Hello World!" << std::endl;
    createConnection("ABC", "DEF");
    return 0;
}

Как видно из комментариев, главный вопрос заключается в том, почему вызов конструктора не дает ошибку "Недопустимое использование неполного типа" класса TestImpl "...". Для справки я использую GCC 5.2 без каких-либо конкретных флагов.

Ответ 1

Проще говоря, GCC не должен отклонять вашу программу, и Clang не должен ее принимать. Он плохо сформирован, никакой диагностики не требуется. Поскольку TestImpl является неполным, ваш шаблон нарушает

[temp.res]/8

... Программа плохо сформирована, не требуется диагностика, если:

  • гипотетическое создание шаблона сразу после его определения будет плохо сформировано из-за конструкции, которая не зависит от параметра шаблона, или
  • интерпретация такой конструкции в гипотетическом экземпляре отличается от интерпретации соответствующей конструкции в любом фактическом экземпляре шаблона.

Можно утверждать, что вызываемый конструктор зависит, но имя класса, безусловно, нет!

В нашем случае гипотетическая инстанция с пакетом из двух строк сразу после определения шаблона даст разные результаты, чем в момент создания в вашей программе. Это связано с тем, что само имя класса (которое, опять же, не зависит) имеет разное значение в двух контекстах.

Это не допустимое определение шаблона. Но GCC осуществляет некоторую свободу действий здесь, поскольку никакой диагностики не требуется, и вспахивания.


Это кратко изложено в примечании под пулями, которое, хотя и не является нормативным, описывает ваше дело:

Это может произойти в ситуациях, в том числе:

  • тип, используемый в не зависящем от имени имени, является неполным в точке, где шаблон определен, но завершен в момент, когда выполняется инстанцирование, или