Ответ 1
Да. Могут быть включены только понятия. Вызов foo<int>
неоднозначен, потому что ни одна из деклараций не является "по меньшей мере такой же ограниченной, как" другая ".
Если, однако, C1
и C2
были concept
вместо inline constexpr bool
s, то объявление foo()
которое возвращает 0
было бы по меньшей мере таким же ограниченным, как объявление foo()
которое возвращает 1
, а вызов foo<int>
будет действительным и вернет 0
. Это одна из причин предпочесть использовать понятия в качестве ограничений для произвольных булевых константных выражений.
Фон
Причина этой разницы (понятия subsume, произвольные выражения нет) лучше всего выражается в сопоставлении семантических ограничений для понятий, которые стоит полностью прочитать (я не буду воспроизводить все аргументы здесь). Но, взяв пример из статьи:
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.link].
Ключевым моментом здесь является то, что атомарные ограничения формируются. Это ключевой момент здесь. В [temp.constr.normal]:
Нормальная форма выражения
E
является ограничением, которое определяется следующим образом:
- Нормальная форма выражения (E) является нормальной формой E.
- Нормальная форма выражения E1 || E2 - дизъюнкция нормальных форм E1 и E2.
- Нормальная форма выражения E1 && E2 является конъюнкцией нормальных форм E1 и E2.
- Нормальная форма идентификационного выражения вида С <A 1, А 2, А..., N>, где C называет понятие, является нормальной формой ограничения экспрессии С, после подстановки A 1, A 2 ,..., A n для C соответствующих параметров шаблона в сопоставлениях параметров в каждом атомном ограничении. Если какая-либо такая подстановка приводит к недопустимому типу или выражению, программа плохо сформирована; диагностика не требуется. [...]
- Нормальная форма любого другого выражения
E
является атомарным ограничением, выражение которого равноE
и отображение параметров которого является тождественным отображением.
Для первой перегрузки foo
ограничение C1<T> && C2<T>
, поэтому для его нормализации получим конъюнкцию нормальных форм C1<T>
1 и C2<T>
1, а затем мы сделанный. Аналогично, для второй перегрузки foo
ограничение является C1<T>
2, которое является его собственной нормальной формой.
Правило для того, что делает атомарные ограничения идентичными, состоит в том, что они должны быть сформированы из одного и того же выражения (конструкция исходного уровня). Хотя обе функции имеют атомное ограничение, которое использует последовательность токенов C1<T>
, это не то же самое буквальное выражение в исходном коде.
Следовательно, индексы, указывающие, что это, по сути, не одно и то же атомное ограничение. C1<T>
1 не идентичен C1<T>
2. Правило не является эквивалентом эквивалента! Таким образом, первый foo
C1<T>
не включает второй foo
C1<T>
и наоборот.
Следовательно, двусмысленный.
С другой стороны, если бы мы имели:
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>
. 3-я пуля дает нам соединение 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
.