Почему этот C ++ явный код специализации шаблона недопустим? - PullRequest
7 голосов
/ 03 мая 2011

(Примечание: я знаю, как это незаконно, я ищу причину, по которой язык делает это так.)

template<class c> void Foo();  // Note: no generic version, here or anywhere.

int main(){
  Foo<int>();
  return 0;
}

template<> void Foo<int>();

Ошибка:

error: explicit specialization of 'Foo<int>' after instantiation

Быстрый переход с Google нашел эту цитату спецификации , но она предлагает только то, что, а не почему.

Edit:

В нескольких ответах был представлен аргумент (например, подтвердил мои предположения) о том, что правило действует именно так, потому что в противном случае было бы нарушено Правило единого определения (ODR) . Тем не менее, это очень слабый аргумент, потому что в этом случае он не выполняется по двум причинам:

  1. Перемещение явной специализации в другой модуль перевода решает проблему и, по-видимому, не нарушает ODR (или так говорит компоновщик).
  2. Краткая форма ODR (применительно к функциям) заключается в том, что у вас не может быть более одного тела для любой данной функции, а у меня нет. Единственное место, в котором когда-либо определено тело функции, - это явная специализация, поэтому вызов Foo<int> не может определить универсальную специализацию шаблона, потому что нет универсального тела, которое было бы специализировано.

Предположение по этому вопросу:

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

Как ни странно, работает следующее (или что-то подобное в реальном коде, над которым я работаю):

Файл A:

template<class c> void Foo();

int main(){
  Foo<int>();
  return 0;
}

Файл B:

template<class c> void Foo();

template<> void Foo<int>();

Но использование этого вообще начинает создавать структуру импорта спагетти.

Ответы [ 3 ]

5 голосов
/ 03 мая 2011

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

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

int main(){
  Foo<int>();    // Define Foo<int>();
  return 0;
}

template<> void Foo<int>(); // Trying to specialize already defined Foo<int>
2 голосов
/ 03 мая 2011

Вы должны рассматривать явную специализацию как объявление функции. Точно так же, как если бы у вас было две перегруженные функции (без шаблонов), если перед попыткой вызова второй версии можно найти только одно объявление, компилятор скажет, что не может найти требуемую перегруженную версию. Разница с шаблонами заключается в том, что компилятор может генерировать эту специализацию на основе шаблона общей функции. Итак, почему это запрещено делать? Поскольку полная специализация шаблона нарушает 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>, и ему все равно, является ли это специализированной версией или нет. Компоновщик счастлив, если он находит тот же символ для ссылки. Это так, , потому что было бы кошмаром сделать это иначе (поискать обсуждение «экспортированных шаблонов») как для программистов (не имеющих возможности легко изменять функции в своих скомпилированных библиотеках), так и для поставщики компиляторов (для реализации этой сумасшедшей схемы компоновки).

2 голосов
/ 03 мая 2011

Этот код недопустим, так как после его создания появляется явная специализация. По сути, компилятор сначала увидел шаблон-шаблон, затем он увидел его создание и специализировал этот шаблон-шаблон определенного типа. После этого он увидел конкретную реализацию универсального шаблона, который уже был создан. Так что же должен делать компилятор? Вернуться и пересобрать код? Вот почему это не разрешено.

...