Подразделение ограничений относится только к понятиям? - PullRequest
0 голосов
/ 28 августа 2018

Рассмотрим этот пример:

template <typename T> inline constexpr bool C1 = true;    
template <typename T> inline constexpr bool C2 = true;

template <typename T> requires C1<T> && C2<T> 
constexpr int foo() { return 0; }

template <typename T> requires C1<T> 
constexpr int foo() { return 1; }

constexpr int bar() {
    return foo<int>();
}

Является ли вызов foo<int>() двусмысленным, или ограничение C1<T> && C2<T> относится к C1<T>?

1 Ответ

0 голосов
/ 28 августа 2018

Да. Только понятия могут быть включены в категорию. Призыв к 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 - это ограничение, которое определяется следующим образом:

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.

...