Почему следующий код вызывает создание шаблона?

У меня есть следующий код на С++:

//Define to 1 to make it work
#define WORKS 0

#if WORKS
    template< typename T > struct Foo;
#else
    template< typename T >
    struct Foo {
        T t;
    };
#endif

class Bar;  //Incomplete type

void fFooBar(Foo<Bar> const & foobar) { }

void f(Foo<Bar> const & foobar) {
    fFooBar(foobar);
}

int main() {
    return 0;
}

Если WORKS определяется как 0 (шаблон структуры задан), код не компилируется, поскольку он пытается создать экземпляр в fFooBar(foobar); и терпит неудачу, потому что Bar является неполным.

Если WORKS определяется как 1 (шаблон структуры undefined), код компилируется.

В соответствии со стандартом шаблон не должен устанавливаться, если не требуется полный тип (что не относится к const&) или это изменит семантику кода (что опять не так, и againt, то же самое должно произойти, если шаблон был undefined).

Кроме того, странно, что программа может быть скомпилирована путем удаления информации из блока компиляции. Но тот факт, что MSVC, gcc и clang все делают то же самое, заставляет меня думать, что для этого должна быть причина.

Ответ 1

Когда WORKS=0, программа может быть выполнена для компиляции в Clang, присвоив вызов fFooBar с помощью ::. Стандарт требует, чтобы поиск имени вел себя по-разному, когда в вызове функции используется неквалифицированное имя.

[basic.lookup.argdep]/1

Когда постфиксное выражение в вызове функции (5.2.2) является неквалифицированным-id, другие пространства имен не считаются во время обычного неквалифицированного поиска (3.4.1), и в этих пространствах имен пространство имен (11.3), которые не отображаются в других местах, могут быть найдены.

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

[basic.lookup.argdep]/2

Для каждого типа аргумента T в вызове функции существует набор из нулевых или более связанных пространств имен и a набор нулевых или более связанных классов. Определены наборы пространств имен и классов полностью по типам аргументов функции [...]

  • Если T - тип класса (включая объединения), его ассоциированные классы: сам класс; класс которого он является член, если таковой имеется; и его прямые и косвенные базовые классы.

Одна из интерпретаций этого заключается в том, что класс должен быть полным, если он используется в типе аргумента для вызова неквалифицированной функции. Альтернативная интерпретация заключается в том, что ADL должен вызывать только создание экземпляров завершенных шаблонов.

Любое поведение соответствует стандарту в соответствии с рабочим проектом N3337

[temp.inst]/6

Если процесс разрешения перегрузки может определить правильную функцию для вызова без создания экземпляра класса определение шаблона, неуказано, действительно ли это создание.

template <class T> struct S {
    operator int();
};

void f(int);
void f(S<int>&);
void f(S<float>);
void g(S<int>& sr) {
    f(sr); // instantiation of S<int> allowed but not required
           // instantiation of S<float> allowed but not required
};

[temp.inst]/7

Если требуется неявное создание экземпляра специализации шаблона шаблона, и шаблон объявляется, но не определяется, программа плохо сформирована.