Да. Только понятия могут быть включены в категорию. Призыв к foo<int>
неоднозначен, потому что ни одно из объявлений «по крайней мере не так ограничено», как другое.
Однако, если бы C1
и C2
были concept
с вместо inline constexpr bool
с, то объявление foo()
, которое возвращает 0
, было бы по меньшей мере таким же ограниченным, как и объявление foo()
, который возвращает 1
, и вызов foo<int>
будет действительным и вернет 0
. Это одна из причин, по которой вы предпочитаете использовать понятия в качестве ограничений для произвольных логических константных выражений.
<ч />
Фон
Причина такого различия (понятия включаются, а произвольные выражения нет) лучше всего выражается в Соответствие семантическим ограничениям для понятий , которое стоит прочитать полностью (я не буду здесь приводить все аргументы). Но на примере из бумаги:
namespace X {
template<C1 T> void foo(T);
template<typename T> concept Fooable = requires (T t) { foo(t); };
}
namespace Y {
template<C2 T> void foo(T);
template<typename T> concept Fooable = requires (T t) { foo(t); };
}
X::Fooable
эквивалентно Y::Fooable
, несмотря на то, что они означают совершенно разные вещи (в силу того, что они определены в другом пространстве имен). Этот вид случайной эквивалентности проблематичен: набор перегрузки с функциями, ограниченными этими двумя понятиями, был бы неоднозначным.
Эта проблема усугубляется, когда одна концепция случайно уточняет другие.
namespace Z {
template<C3 T> void foo(T);
template<C3 T> void bar(T);
template<typename T> concept Fooable = requires (T t) {
foo(t);
bar(t);
};
}
Набор перегрузки, содержащий различные жизнеспособные кандидаты, ограниченные X::Fooable
, Y::Fooable
и Z::Fooable
соответственно, всегда будет выбирать кандидата, ограниченного Z::Fooable
. Это почти наверняка не то, что хочет программист.
<ч />
Стандартные ссылки
Правило подстановки находится в [temp.constr.order] /1.2:
атомное ограничение A включает другое атомное ограничение B тогда и только тогда, когда A и B идентичны с использованием описанных правил в [temp.constr.atomic].
Атомные ограничения определены в [temp.constr.atomic] :
атомарное ограничение формируется из выражения E
и сопоставления параметров шаблона, отображаемых в E
, с аргументами шаблона, включающими параметры шаблона ограниченного объекта, называемые отображением параметров ( [temp.constr.decl]). [Примечание: Атомные ограничения формируются путем нормализации ограничений. E
никогда не является логическим AND
выражением или логическим OR
выражением. - конец заметки]
Два атомарных ограничения идентичны , если они сформированы из одного и того же выражения , а цели сопоставлений параметров эквивалентны в соответствии с правилами для выражений, описанными в [temp.over. ссылка].
Ключевым моментом здесь является то, что атомные ограничения сформированы . Это ключевой момент прямо здесь. В [temp.constr.normal] :
нормальная форма выражения E
- это ограничение, которое определяется следующим образом:
- Нормальная форма выражения (E) - это нормальная форма E.
- Нормальная форма выражения E1 || Е2 - это дизъюнкция нормальных форм Е1 и Е2.
- Нормальной формой выражения E1 && E2 является соединение нормальных форм E1 и E2.
- Нормальная форма id-выражения формы C 1 , A 2 , ..., A n >, где C называет концепт, это нормальная форма выражения-ограничения для C после замены A 1 , A 2 , .. ., A n для соответствующих параметров шаблона C в отображениях параметров в каждом атомном ограничении. Если любая такая замена приводит к недопустимому типу или выражению, программа некорректна; Диагностика не требуется. [...]
- Нормальная форма любого другого выражения
E
- это атомарное ограничение, выражение которого E
, а отображение параметров - отображение идентификаторов.
Foпри первой перегрузке foo
ограничение равно C1<T> && C2<T>
, поэтому для его нормализации мы получаем соединение нормальных форм C1<T>
1 и C2<T>
1 и тогда мы закончили. Аналогично, для второй перегрузки foo
ограничение равно C1<T>
2 , что является его собственной нормальной формой.
Правило того, что делает атомные ограничения идентичными, состоит в том, что они должны быть сформированы из одного и того же выражения (конструкция уровня источника). Хотя обе функции содержат атомарное ограничение, использующее последовательность токенов C1<T>
, в исходном коде это не одно и то же буквенное выражение 1146 *.
Следовательно, индексы, указывающие, что на самом деле это не одно и то же атомное ограничение. C1<T>
1 не идентичен C1<T>
2 . Правило не является символической эквивалентностью! Таким образом, первый foo
1156 * не включает в себя второй foo
1158 *, и наоборот.
Следовательно, неоднозначно.
С другой стороны, если бы у нас было:
template <typename T> concept D1 = true;
template <typename T> concept D2 = true;
template <typename T> requires D1<T> && D2<T>
constexpr int quux() { return 0; }
template <typename T> requires D1<T>
constexpr int quux() { return 1; }
Ограничение для первой функции: D1<T> && D2<T>
. Третья пуля дает нам соединение D1<T>
и D2<T>
. Затем четвертая пуля приводит нас к замене самих понятий, поэтому первая нормализуется в true
1 , а вторая в true
2 . И снова подписи указывают , на который ссылается true
.
Ограничение для второй функции - D1<T>
, которое нормализует (4-я пуля) в true
1 .
И теперь, true
1 действительно то же самое выражение, что и true
1 , поэтому эти ограничения считаются идентичными. В результате D1<T> && D2<T>
включает D1<T>
, а quux<int>()
является однозначным вызовом, который возвращает 0
.