Подтвердить что ты не робот

Из Josuttis: выполняют ли различные функции шаблона, которые создают экземпляр одной и той же сигнатуры функции, заданной определенным типам, приводят к недействительности ODR?

В Josuttis 'и Vandevoorde известная книга о шаблонах, С++ Templates: The Complete Guide, они обсуждают детали, касающиеся перегрузки шаблонов функций.

В одном из своих примеров, связанных с обсуждением сигнатур функций и перегруженных шаблонов функций, они представляют код, который они описывают в следующих выражениях:

This program is valid and produces the following output:

(Note: Output shown below)

Однако, когда я создаю и компилирую идентичный код в Visual Studio 2010, я получаю другой результат. Это заставляет меня думать, что либо компилятор VS 2010 создает неправильный код, либо Josuttis неверен, что код действителен.

Вот код. (Josuttis 2003, раздел 12.2.1)

// File1.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T2, T1)
{
    std::cout << "f1(T2, T1)" << std::endl;
}

extern void g();

int main()
{
    f1<char, char>('a', 'b');
    g();
}

...

// File2.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
    std::cout << "f1(T1, T2)" << std::endl;
}

void g()
{
    f1<char, char>('a', 'b');
}

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

Согласно Йосуттису:

This program is valid and produces the following output:

f1(T2, T1)
f1(T1, T2)

Когда я создаю и запускаю идентичный код без изменений в компиляторе Visual Studio 2010, вот мой результат:

f1(T1, T2)
f1(T1, T2)

Кроме того, мне было интересно, как возможно, чтобы компилятор/компоновщик различал функцию f1 как созданную в файле file1.cpp, а функцию f1 как экземпляр в файле2.cpp, учитывая, что (я думаю ) компилятор отбрасывает все "знания" о том, что эти функции были созданы из шаблонов и имеет только информацию (я думаю) самой функции: void (char, char), которая одинакова для обеих функций f1.

Так как (если я прав) сигнатура функции идентична в двух единицах перевода, я бы подумал, что это пример нарушения Одно правило определения (ODR), и поэтому оно было бы недопустимым С++.

Однако, как я только что отметил, Josuttis и Vandevoorde утверждают, что это действительно С++.

Но так как моя скомпилированная версия того же кода дает разные результаты, чем утверждение Josuttis является результатом, это, по-видимому, указывает на то, что либо VS 2010 создает неправильный код, либо Josuttis является неправильным в этом случае (т.е. код недействителен и нарушает ODR).

Являются ли Josuttis и Vandevoorde некорректными, или VS 2010 производит неправильный вывод? Или есть какое-то другое объяснение, объясняющее несоответствие между выводами VS 2010, и вывод отчета Josuttis?

Может показаться интересным продемонстрировать разборку VS 2010 в точке, в которой вызывается каждый f1().

Первый вызов f1() (непосредственно внутри main()):

f1() called directly from within main()

Второй вызов f1() (изнутри g()):

f1() called from within g()

Обратите внимание, что адрес f1(), выбранный компилятором в обоих случаях, тот же - 13E11EAh. Для меня это указывает на то, что на самом деле компилятор не может отличить две созданные экземпляры функций, и это случай, когда ODR нарушается, поэтому код недопустим. С++ и Josuttis имеет ошибку в своей книге. Но это просто - указание. Я не знаю.

(Я проверил ошибки на веб-сайте книги, и об этом примере не упоминается.)

ДОБАВЛЕНИЕ. В запросе из комментария я добавляю соответствующий вывод из файла .map для этой программы, который показывает измененные имена /s, используемые для f1:

.map file output showing mangled names for <code>f1</code>

ДОБАВЛЕНИЕ 2 Теперь, когда на вопрос будет дан ответ - книга Йосуттиса верна - я хочу отметить, что в тексте Йосуттиса в том же разделе (12.2.1) он четко очерчен точно что определяет уникальную сигнатуру функции, включая аспект шаблона.

Из текста (между другими, ожидаемыми вещами, которые определяют подпись функции), ПЕРЕВОДНАЯ ГРУППА является частью сигнатуры функции; для функций шаблона (только) ТИП RETURN является частью сигнатуры функции, а

0,6. Параметры шаблона и аргументы шаблона, если функция генерируется из шаблона функции.

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

4b9b3361

Ответ 1

Извините за предыдущий неправильный ответ. Пример действительно кажется правильным, и в самом стандарте есть аналогичный пример (С++ 11, 14.5.6.1/1-2). Позвольте мне просто процитировать его целиком:

  • Можно перегрузить шаблоны функций, чтобы две разные специализированные шаблоны были одинаковыми. [Пример:

    // file1.c
    
    template<class T> void f(T*);
    
    void g(int* p) {
        f(p); // calls f<int>(int*)
    }
    
    
    // file2.c
    
    template<class T> void f(T);
    
    void h(int* p) {
        f(p); // calls f<int*>(int*)
    }
    

    - конец примера]

  • Такие специализации представляют собой различные функции и не нарушают одно правило определения (3.2).

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