В C++20 вы можете написать шаблон функции с ограничениями несколькими способами:
template <typename T>
concept Fooable = true;
template <typename T>
requires Fooable<T>
void do_something(T&); // (1)
template <typename T>
void do_something(T&) requires Fooable<T>; // (2)
Согласно принятому ответу в этот вопрос, эти две формы эквивалентны (что всегда было моим пониманием).
Однако я заметил, что GCC 12.1 считает (1) и (2) двумя разными функциями, а не (2) повторным объявлением: можно предоставить определения для обеих, и тогда попытка вызова do_something()
будет неоднозначной (пример).
Обновлено:
(Я смутно припоминаю, что во времена Concepts TS требования подвергались «нормализации», чтобы решить, когда они были эквивалентны — я думаю, в C++20 это уже не так?)
Хорошо, но почему это так?
Ответ юриста, не связанного с языком, заключается в том, что первый может применяться только к спискам параметров шаблона, а второй - к любой функции (которая может не быть шаблоном). Применение второй формы к не-шаблону произойдет, например, если мы отключим (не-шаблонную) функцию-член шаблона класса. Для кроличьей норы LL скоро кто-нибудь появится, я уверен.
Конечно, но правило могло бы звучать примерно так: «Форма (1) интерпретируется так, как если бы вы написали ее в форме (2)» (что, как я смутно припоминаю, имело место во времена Концепции ТС?). Вот почему я был немного удивлен, что GCC допускает такую перегрузку.
Могло, но не было. Вероятно, по тем же причинам сокращенные шаблоны — это не просто «переписанные» обычные шаблоны, эквивалентности в них тоже нет. И я готов поспорить, что все это, вероятно, будет хорошо сочетаться с абзацами ODR, которые требуют одинакового определения вплоть до последовательности токенов.
Формулировка в этой области немного изменилась. В C++20 у нас было это правило в [temp.over.link]/7:
Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters. Two function templates are functionally equivalent if they are declared in the same scope, have the same name, accept and are satisfied by the same set of template argument lists, and have return types and parameter lists that are functionally equivalent using the rules described above to compare expressions involving template parameters. If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
[Note 7: This rule guarantees that equivalent declarations will be linked with one another, while not requiring implementations to use heroic efforts to guarantee that functionally equivalent declarations will be treated as distinct.
// guaranteed to be the same template <int I> void f(A<I>, A<I+10>); template <int I> void f(A<I>, A<I+10>); // guaranteed to be different template <int I> void f(A<I>, A<I+10>); template <int I> void f(A<I>, A<I+11>); // ill-formed, no diagnostic required template <int I> void f(A<I>, A<I+10>); template <int I> void f(A<I>, A<I+1+2+3+4>);
-end note]
В вашем примере:
template <typename T>
requires Fooable<T>
void do_something(T&); // (1)
template <typename T>
void do_something(T&) requires Fooable<T>; // (2)
Они функционально эквивалентны (у них, в основном, одинаковые ограничения), но не эквивалентны (у них разные заголовки шаблонов — предложение require после параметров шаблона является частью заголовка шаблона), что делает этот неправильный формат не требующим диагностики. На практике, поскольку они не эквивалент, это разные перегрузки, но поскольку они функционально эквивалентны, любая попытка вызова между ними будет неоднозначной.
Как я указываю в другом ответе, они имеют одинаковое значение - просто вам нужно придерживаться одной формы для объявления и определения, если вы их разделите.
Формулировка Текущий после сводной статьи Дэвиса Херринга P1787 предполагает переход к [базовая.область.область]/4:
Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless [...] each declares a function or function template, except when [...] both declare function templates with equivalent non-object-parameter-type-lists, return types (if any), template-heads, and trailing requires-clauses (if any), and, if both are non-static members, they have corresponding object parameters.
Это делает два do_something
s не соответствует, что делает их разными шаблонами функций. Мы не сталкиваемся с новым функционально эквивалентным, но не эквивалентным правилом (поэтому мы не плохо сформированы, диагностика не требуется), но у нас просто есть два шаблона функций, которые обязательно неоднозначны во всех случаях. Так что... не самая полезная вещь на свете.
вау, это то, о чем я даже не подозревал. Рассуждения обретают смысл после прочтения — отсутствие необходимости в диагностике означает, что у меня есть несколько строк кода в некоторых проектах, которые нужно изучить сейчас :(
Спасибо Барри! Я думал, что существует правило перезаписи, и действительно, в Концепции TS это было повторное объявление (godbolt.org/z/W1a63cP47). Вы знаете, когда/почему это было изменено?
@TristanBrindle Да (open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4641.pdf, ищите temp.over.link). Вероятно, потому что это просто добавляет компилятору больше работы для проверки, но на самом деле не добавляет никакой ценности? Точно не знаю, могу спросить у Андрея.
@ Барри, я не знаю, как ты так быстро находишь эти ссылки! IFNDR кажется довольно суровым, хотя
Какое правило утверждает, что два рассматриваемых шаблона функций функционально эквивалентны? [temp.over.link]/6 говорит, что два шаблон-головки функционально эквивалентны, если они принимают и удовлетворяются одним и тем же набором списков аргументов шаблона. В случае (2) правильно ли говорить, что шаблон-голова не принимает T
, который не является Fooable
? Ограничение в этом случае не является частью шаблон-голова.
@BrianBi Теперь это правило могло быть переведено в соответствующую формулировку (угорь.is/С++ черновик/базовый.scope.scope#4.3)
@TristanBrindle Одна проблема здесь, кстати, заключается в том, что template <class T> requires meow<T>(v) void f(T v)
и template <class T> void f(T v) requires meow<T>(v)
на самом деле могут означать разные вещи, несмотря на то, что они эквивалентны токену (см. Также этот ответ)
[basic.scope.scope]/4.3, похоже, не поддерживает вывод о том, что программа представляет собой отчет о недоставке с неправильным форматом. Списки типов параметров, не являющихся объектными, и возвращаемые типы эквивалентны. шаблон-головки не являются ни эквивалентными, ни функционально эквивалентными, поскольку один из них ограничен, а другой нет. Замыкающие требует-оговорки не являются ни эквивалентными, ни функционально эквивалентными, поскольку один присутствует, а другой нет. Эти четыре критерия рассматриваются отдельно.
@BrianBi Формулировка здесь сильно изменилась, и ее трудно отследить. В статье Пре-Дэвиса применялась функционально эквивалентная формулировка. Теперь, кажется, просто упустить это дело. Я не думаю, что это имеет большое значение, поскольку в конечном итоге эти два не являются эквивалент, но, поскольку они удовлетворяются одними и теми же типами, они «функционально эквивалентны», поэтому запись в обоих направлениях не делает то, что вы хотите.
Я согласен, что это не имеет большого значения, но для целей этого вопроса это действительно влияет на ответ. Простое объявление (1) и (2) в одной и той же единице перевода не делает программу IFNDR. Программа становится IF, если делается попытка вызвать do_something
, поскольку разрешение перегрузки никогда не удастся.
Я подозреваю, что намерение (по-прежнему) состоит в том, чтобы они были IFNDR, и что различные эквивалентности следует рассматривать вместе (хотя бы потому, что вы не можете установить эквивалентность компонентов шаблона независимо от его шаблон-голова). Проблема может быть в порядке.
В принятом ответе на этот вопрос также говорится: «Невозможно объявить с одним синтаксисом и определить с другим». Возможно, это не является явным, но, безусловно, говорит, что он объявляет разные функции.