Вы должны рассматривать явную специализацию как объявление функции. Точно так же, как если бы у вас было две перегруженные функции (без шаблонов), если перед попыткой вызова второй версии можно найти только одно объявление, компилятор скажет, что не может найти требуемую перегруженную версию. Разница с шаблонами заключается в том, что компилятор может генерировать эту специализацию на основе шаблона общей функции. Итак, почему это запрещено делать? Поскольку полная специализация шаблона нарушает ODR, когда он виден, так как к тому времени уже существует специализация шаблона для того же типа. Когда создается экземпляр шаблона (неявно или нет), также создается соответствующая специализация шаблона, так что последующее использование (в том же модуле перевода) той же специализации сможет повторно использовать создание экземпляра и не дублировать код шаблона для каждого создания , Очевидно, что ODR применяется так же хорошо к специализациям шаблонов, как и в других местах.
Таким образом, когда цитируемый текст говорит: «Диагностика не требуется», это просто означает, что компилятору не требуется предоставлять вам проницательное замечание о том, что проблема связана с созданием экземпляра шаблона, произошедшего за некоторое время до явной специализации. , Но, если он этого не делает, другой вариант - дать стандартную ошибку нарушения ODR, то есть «множественные определения специализации« Foo »для [T = int]» или что-то в этом роде, что не будет полезно как более умная диагностика.
ОТВЕТ НА РЕДАКТИРОВАНИЕ
1) Хотя говорится, что все определения функций шаблона (т.е. реализация) должны быть видимы в момент создания экземпляра (чтобы компилятор мог подставить аргумент (ы) шаблона и создать экземпляр шаблона функции). Однако неявное создание экземпляра шаблона функции требует только доступности объявления функции. Таким образом, в вашем случае разделение на две единицы перевода работает, потому что это не нарушает ODR (поскольку в этом TU существует только одно объявление Foo<int>
), объявление, если Foo<int>
доступно в неявной точке создания (через Foo<T>
), и определение Foo<int>
доступно для компоновщика в TU B. Таким образом, никто не утверждал, что этот второй пример "не должен работать", он работает так, как должен. Ваш вопрос касается правила, которое применяется в пределах одной единицы перевода, не противоречите аргументам, говоря, что ошибка не возникает, когда вы разбиваете ее на два TU (особенно, когда это явно должно работать нормально в двух TU, согласно правила).
2) В вашем первом примере либо будет ошибка, потому что компилятор не может найти общий шаблон функции (неспециализированная реализация) и, следовательно, не может создать экземпляр Foo<int>
из общего шаблона. Или компилятор найдет определение для общего шаблона, использует его для создания экземпляра Foo<int>
, а затем выдаст ошибку, поскольку обнаружена вторая специализация шаблона Foo<int>
. Вы, кажется, думаете, что компилятор найдет вашу специализацию, прежде чем он доберется до него, это не так. Компиляторы C ++ компилируют код сверху вниз, они не идут туда-сюда, чтобы заменить что-то здесь и там. Когда компилятор доходит до первого использования Foo<int>
, он видит только общий шаблон в этой точке, предполагая, что будет реализация этого общего шаблона, который можно использовать для создания экземпляра Foo<int>
, он не ожидает специализированной реализации для Foo<int>
он ожидает и будет использовать общий. Затем он видит специализацию и выдает ошибку, потому что он уже решил, что должна использоваться общая версия, поэтому он видит два разных определения для одной и той же функции, и да, он нарушает ODR. Это так просто.
ПОЧЕМУ ОЙ ПОЧЕМУ
Случай с 2 TU должен работать , потому что вы должны иметь возможность обмениваться экземплярами шаблонов между TU, это особенность C ++ и полезная (в случае, когда у вас есть небольшое количество возможных экземпляров) Вы можете предварительно скомпилировать их).
Случай 1 TU не может быть разрешен , потому что объявление чего-то в C ++ говорит компилятору "где-то определена эта вещь". Вы говорите компилятору «где-то есть общее определение шаблона», затем говорите: «Я хочу использовать общее определение для создания функции Foo<int>
», и, наконец, вы говорите «всякий раз, когда вызывается Foo<int>
, он должен используйте это специальное определение ". Это полное противоречие! Вот почему *1036* ODR существует и применяется к этому контексту, чтобы запретить подобные противоречия. Неважно, отсутствует ли общее определение «быть найденным», его ожидает компилятор, и он должен предположить, что оно существует и отличается от специализации. Это не может продолжаться и говорить «хорошо, так что я буду искать в другом месте в коде общее определение, и если я не смогу его найти, тогда я вернусь и« одобрю »эту специализацию для использования вместо общей определение, но если я найду его, я буду отмечать эту специализацию как ошибку ". Он также не может продолжать и полностью игнорировать желание программиста, изменяя код, который ясно показывает намерение использовать общий шаблон (так как специализация еще не объявлена) для кода, который использует специализацию, которая появится позже. Я не могу объяснить «почему» более четко, чем это.
Корпус 2 TU совершенно другой. Когда компилятор компилирует TU A (который использует Foo<int>
), он будет искать общее определение, не сможет его найти, предположит, что он будет связан позже как Foo<int>
, и оставит символ-заполнитель. Затем, поскольку компоновщик не будет искать шаблоны (шаблоны на практике НЕ экспортируются), он будет искать функцию, которая реализует Foo<int>
, и ему все равно, является ли это специализированной версией или нет. Компоновщик счастлив, если он находит тот же символ для ссылки. Это так, , потому что было бы кошмаром сделать это иначе (поискать обсуждение «экспортированных шаблонов») как для программистов (не имеющих возможности легко изменять функции в своих скомпилированных библиотеках), так и для поставщики компиляторов (для реализации этой сумасшедшей схемы компоновки).