Как определения шаблона соответствуют объявлениям шаблона? - PullRequest
7 голосов
/ 07 марта 2012

Насколько точно объявление шаблона соответствует определению шаблона?Я нашел некоторый текст в стандарте о template-ids , относящемся к той же функции, если «их template-names [...] ссылаются на тот же шаблон и [...]"(14.4 [temp.type] p1), но я не могу найти определение для template-names или когда template-names ссылаются на тот же шаблон.Я не уверен, что в любом случае нахожусь на правильном пути, потому что я недостаточно расшифровал грамматику, чтобы определить, является ли template-id частью определения / объявления шаблона, или простоиспользование шаблона.

Например, следующая программа работает нормально.

#include <iostream>

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

template<typename T>
T foo(T t)
{ std::cout << "A\n"; return 0; }

Если я изменю способ использования параметров шаблона в определении шаблона, имена, по-видимому, больше не относятся кк тому же шаблону, и соединение не удается.

#include <iostream>

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }

// or

template<typename T>
struct identity {
    typedef T type;
};

template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }

Далее, если я переместу определение шаблона в другой модуль перевода, для моей реализации C ++ (MSVC 11 beta) программа работает независимо от того, как ятипы.

//main.cpp

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

//definition.cpp
#include <iostream>

template<typename T>
struct identity {
    typedef T type;
};

template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }

template int foo<int>(int);

или

//definition.cpp
#include <iostream>

template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }

template int foo<int>(int);

или даже если определение вообще не является шаблоном:

//definition.cpp
#include <iostream>

int foo(T t) { std::cout << "A\n"; return 0; }

Очевидно, что связывание успешно, потому что подпись успешнаИмя / mangled одинаково независимо от шаблона, который был создан для создания символа.Я думаю, что это неопределенное поведение, потому что я нарушаю:

§ 14.1 [temp] p6

Шаблон функции, функция-член шаблона класса иличлен статических данных шаблона класса должен быть определен в каждой единице перевода, в которой он создается неявно (14.7.1), если только соответствующая специализация явно не определена (14.7.2) в некоторой единице перевода;Диагностика не требуется.

Но затем скажите, что я пытаюсь выполнить эти требования, поместив определение шаблона во второй модуль перевода и включив явную реализацию в одном из двух мест:

#include <iostream>

template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }

// Location 1    

template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }

// Location 2

Каковы правила устранения неоднозначности, к какому шаблону относится явная инстанциация?Помещение его в Местоположение 1 приводит к созданию экземпляра правильного шаблона и использованию этого определения в конечной программе, а помещение его в Местоположение 2 создает экземпляр другого шаблона и вызывает то, что, как я считаю, является неопределенным поведением в соответствии с пунктом 14.1 p6 выше.

С другой стороны, неявная реализация двух определений шаблонов выбирает первый шаблон независимо от того, что такое, поэтому кажется, что правило устранения неоднозначности шаблонов отличается в этих обстоятельствах:

#include <iostream>

template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }

template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }

int main() {
    foo(1); // prints "A"
}

Причина этоговозник вопрос, связанный с этим вопросом , когда спрашивающий обнаружил, что одно предварительное объявление

template<typename T>
T CastScriptVarConst(const ScriptVar_t& s);

не может выступать в качестве объявления нескольких определений шаблона:

template<typename T>
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return (T) s;
}

template<typename T>
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ)
                        && std::is_base_of<CustomVar,T>::value,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return *s.as<T>();
}

И я хотел лучше понять связь между определениями шаблонов и объявлениями.

1 Ответ

4 голосов
/ 08 марта 2012

ОК, начнем с самого начала. «Template-name» шаблона - это фактическое имя функции или класса, который подвергается шаблону; в

template<class T> T foo(T t);

foo - это имя шаблона. Для шаблонов функций правило определения того, являются ли они одинаковыми, довольно длинное, описанное в 14.5.5.1 «Перегрузка шаблона функции». Параграф 6 этого раздела (здесь я цитирую C ++ 03, так что формулировки и номера абзацев могли измениться в C ++ 11) определяют термины эквивалент и функционально эквивалентные , когда применяется к выражениям, включающим параметры шаблона.

Короче говоря, эквивалентные выражения одинаковы, за исключением возможного использования разных имен для параметров шаблона, а функционально эквивалентные выражения одинаковы, если они оказываются одинаковыми. Например, первые два объявления f эквивалентны , но третье только функционально эквивалентно другим двум: -

template<int A, int B>
void f(array<A + B>);
template<int T1, int T2>
void f(array<T1 + T2>);
template<int A, int B>
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >);

В параграфе 7 продолжается расширение этих двух определений на целые шаблоны функций. Два соответствующих функциональных шаблона (в именах, области видимости и списках параметров шаблона) эквивалентны, если они также имеют эквивалентные типы возвращаемых данных и типы аргументов, или функционально эквивалентны, если они имеют только функционально эквивалентные возвращаемые типы и типы аргументов. Глядя на ваш второй пример, эти две функции только функционально эквивалентны: -

template<typename T>
T foo(T t);

template<typename T>
typename identity<T>::type foo(T t);

Пункт 7 завершается страшным предупреждением о том, что «если программа содержит объявления шаблонов функций, которые являются функционально эквивалентными, но не эквивалентными, программа является некорректной; диагностика не требуется». Ваш второй пример, таким образом, не является допустимым C ++. Подобное обнаружение ошибок потребовало бы, чтобы каждое объявление и определение шаблона функции были аннотированы в двоичном файле с помощью AST, описывающей выражение шаблона для каждого параметра и типа возвращаемого значения, поэтому стандарт не требует реализации для его обнаружения. MSVC оправдывает компиляцию вашего третьего примера так, как вы хотели, но было бы так же оправдано нарушать.

Переходя к явной реализации, важным разделом является 14.7, «Создание и специализация шаблона». Пункт 5 запрещает все следующее:

  • Явное создание экземпляра шаблона более одного раза;
  • Явное создание экземпляров и специализация одного и того же шаблона;
  • Явная специализация шаблона для одного и того же набора аргументов более одного раза.

Опять же, «диагностика не требуется», так как ее довольно сложно обнаружить.

Итак, чтобы расширить ваш пример явного создания экземпляра, следующий код нарушает второе правило и является недопустимым: -

/* Template definition. */
template<typename T>
T foo(T t)
{ ... }

/* Specialization, OK in itself. */
template< >
int foo(int t)
{ ... }

/* Explicit instantiation, OK in itself. */
template< >
int foo(int t);

Это недопустимо вне зависимости от местоположения явной специализации и явного создания экземпляра, но, конечно, поскольку не требуется никакой диагностики, вы можете получить полезные результаты на некоторых компиляторах. Обратите внимание также на разницу между явной реализацией и явной специализацией. Следующий пример некорректен, потому что объявляет явную специализацию, не определяя ее: -

template<typename T>
T f(T f)
{ ... }

template< >
int f(int);

void g(void)
{ f(3); }

но этот пример правильно сформирован, потому что у него есть явный экземпляр: -

template<typename T>
T f(T f)
{ ... }

template f(int);

void g(void)
{ f(3); }

< > имеет все значение. Также предупреждаем, что даже когда вы определяете явную специализацию, она должна быть до того, как вы ее используете , иначе компилятор мог бы уже сгенерировать неявную реализацию для этого шаблона. Это в параграфе 6 «Явная специализация» 14.7.3, чуть ниже того места, где вы читали, и опять же, диагностика не требуется. Чтобы адаптировать тот же пример, это плохо сформировано: -

template<typename T>
T f(T f)
{ ... }

void g(void)
{ f(3); } // Implicitly specializes int f(int)

template< >
int f(int) // Too late for an explicit specialization
{ ... }

Если вы еще не достаточно смущены, взгляните на свой последний пример: -

template<typename T>
T foo(T t) { ... }

template<typename T>
int foo(int t) { ... }

Второе определение foo является , а не специализацией первого определения.Это должно быть template< > int foo(int), чтобы быть специализацией template<typename T> T foo(T).Но это нормально: перегрузка функций разрешена и разрешена между шаблонами функций и обычными функциями.Вызовы формы foo(3) всегда будут использовать первое определение, потому что его параметр шаблона T может быть выведен из типа аргумента.Второе определение не позволяет выводить его параметр шаблона из типа аргумента.Только явно указав T, вы можете получить второе определение, и только тогда, когда вызов не является неоднозначным с первым определением: -

f<int>(3); // ambiguous
f<string>(3); // can only be the second one

Весь процесс выполнения разрешения перегрузки для шаблонов функций слишкомдолго описывать тут.Прочтите раздел 14.8.3, если вам интересно, и задайте больше вопросов: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...