Почему полная специализация функции шаблона не берется из файла .cpp без объявления?

Следующий код не генерирует ошибок/предупреждений компиляции/компоновщика:

// A.h
#include<iostream>
struct A
{
  template<typename T>
  static void foo (T t)
  {
    std::cout << "A::foo(T)\n";
  }
};
void other ();

// main.cpp
#include"A.h"
int main ()
{
  A::foo(4.7);
  other();
}

// other.cpp
#include"A.h"
template<>
void A::foo (double d)
{
  cout << "A::foo(double)\n";
}

int other ()
{
  A::foo(4.7);
}

Вывод на удивление таков:

A::foo(T)
A::foo(double)

Почему компилятор не может подобрать правильный A::foo(double) в случае main.cpp ?

Согласитесь, что, как и ожидалось, проблем не возникает, если в A.h есть объявление, как показано ниже:

template<> void A::foo (double);

Но это не проблема, потому что во время компоновки у компилятора есть специализированная версия.

Кроме того, не является ли наличие двух разных версий одной и той же функции неопределенным поведением?


person iammilind    schedule 26.10.2012    source источник
comment
Этот синтаксис вообще законен? Разве это не должно быть template <> void A::foo<double>(double d)?   -  person Kerrek SB    schedule 26.10.2012
comment
@KerrekSB, каким-то образом этот синтаксис работает с g++.   -  person iammilind    schedule 26.10.2012
comment
Хм, может быть, это можно вывести... интересно. Я бы на всякий случай расписал, чтобы не путать читателей...   -  person Kerrek SB    schedule 26.10.2012
comment
С g++ я получаю различное поведение в зависимости от того, компилирую ли я с оптимизацией или нет. Без оптимизации нет встраивания, и компоновщик выбирает один из двух экземпляров на основе порядка ссылок. При оптимизации экземпляр функции-члена основного шаблона встраивается в main, поэтому у компоновщика нет возможности выбора.   -  person Vaughn Cato    schedule 26.10.2012
comment
@VaughnCato, это хорошая находка, твой комментарий похож на ответ. Итак, то, что я делаю, - это неопределенное поведение.   -  person iammilind    schedule 26.10.2012


Ответы (3)


Все явные объявления специализации должны быть видимы во время создания экземпляра шаблона. Поскольку ваше явное объявление специализации для A::foo<double> видно в одной единице перевода, но не видно в другой, программа имеет неправильный формат.

(На практике компилятор создаст первичный шаблон в main.cpp, а специально специализированный — в other.cpp. Это все равно будет нарушением ODR.)

person Kerrek SB    schedule 26.10.2012
comment
Разве программа ill-formed не должна приводить к ошибке компилятора/компоновщика? Кроме того, является ли это нарушение ODR UB, если контекстом являются шаблоны. - person iammilind; 26.10.2012
comment
@iammilind: я считаю, что это не является строго нарушением ODR как таковым, потому что оно уже плохо сформировано на концептуально более раннем этапе. Это практически нарушение ODR, если вы понимаете, что я имею в виду. И нет, для этих ошибок не требуется никакой диагностики — очевидно! Как вы только что заметили, никто не может знать, что вы собираетесь сломать программу в другом месте. Код может быть встроенным в обоих случаях и нормально ссылаться, просто не иметь правильного поведения. - person Kerrek SB; 26.10.2012
comment
@iammilind: не имеет значения, включает ли это шаблоны или нет: это неопределенное поведение. В некоторых случаях компоновщик действительно мог диагностировать ошибку, но не во всех случаях по разным причинам: если функции встроены, разные вызовы будут перемешаны с контекстом, в котором они были встроены, и будет невозможно уловить различия. Даже если они не встроены, если оба помечены как inline, компоновщик увидит два символа weak и не посчитает множественное определение нарушением ODR, он случайным образом выберет один и отбросит другой . - person David Rodríguez - dribeas; 26.10.2012
comment
... обратите внимание, что inline означает, что обнаружение нескольких определений в программе обязательно не является нарушением ODR, но если эти определения не точно совпадают, это является нарушение ODR. В настоящее время я не знаю ни одного компилятора с компоновщиком, способным отловить эту форму нарушения ODR. [точно означает, что они не только должны совпадать по тексту, но и что все символы внутри нескольких определений должны ссылаться на одни и те же точные элементы, т. е. если он вызывает 'f()', 'f' должен разрешаться к одному и тому же символу во всех случаях. - person David Rodríguez - dribeas; 26.10.2012

main.cpp не видит код внутри other.cpp. Специализации шаблонов относятся к области файлов.

person Nikos C.    schedule 26.10.2012

Почему компилятор не может подобрать правильный A::foo(double) в случае main.cpp?

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

имеет 2 разные версии одной и той же функции с неопределенным поведением?

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

person David Rodríguez - dribeas    schedule 26.10.2012