Почему концепция same_as дважды проверяет равенство типов?

19

Глядя на возможную реализацию концепции same_as на https://en.cppreference.com/w/cpp/concepts/same_as, я заметил, что происходит нечто странное.

namespace detail {
    template< class T, class U >
    concept SameHelper = std::is_same_v<T, U>;
}

template< class T, class U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;

Первый вопрос: почему SameHelperпонятие вложено? Второе - почему same_asпроверяет, Tсовпадает ли Uи Uсовпадает с T? Разве это не избыточно?

user7769147
источник
Только потому, что SameHelper<T, U>может быть правдой, не значит, SameHelper<U, T>может быть.
Какой-то программист чувак
1
в том-то и дело, что если a равно b, b равно a, не так ли?
user7769147
@ user7769147 Да, и это определяет это отношение.
Франсуа Андриё
4
Хм, документация для std :: is_same даже гласит: «Коммутативность удовлетворена, то есть для любых двух типов T и U, is_same<T, U>::value == trueесли и только если is_same<U, T>::value == true». Это подразумевает, что эта двойная проверка не нужна
Кевин
1
Нет, это неправильно, std :: is_same говорит: если и только если условие выполняется, два типа являются коммутативными. Это не обязательно так. Но я не могу найти пример двух некоммутативных типов.
Неманья Борич

Ответы:

16

Интересный вопрос. Недавно я смотрел доклад Эндрю Саттона о концепциях, и в ходе сессии вопросов и ответов кто-то задал следующий вопрос (временная метка в следующей ссылке): CppCon 2018: Эндрю Саттон «Концепции в 60: все, что вам нужно знать, и ничего, чего вы не делаете»

Таким образом, вопрос сводится к следующему: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?Эндрю ответил да, но указал на тот факт, что компилятор имеет некоторые внутренние методы (которые прозрачны для пользователя), чтобы разложить понятия на атомарные логические предложения ( atomic constraintsкак Эндрю сформулировал термин) и проверить, являются ли они эквивалент.

Теперь посмотрим, что говорит cppreference std::same_as:

std::same_as<T, U>включает в себя std::same_as<U, T>и наоборот.

Это в основном отношения «если и только если»: они подразумевают друг друга. (Логическая эквивалентность)

Моя гипотеза состоит в том, что здесь атомные ограничения std::is_same_v<T, U>. То, как обрабатывают компиляторы, std::is_same_vможет заставить их думать std::is_same_v<T, U>и std::is_same_v<U, T>как два разных ограничения (это разные сущности!). Так что если вы реализуете, std::same_asиспользуя только один из них:

template< class T, class U >
concept same_as = detail::SameHelper<T, U>;

Тогда std::same_as<T, U>иstd::same_as<U, T> будет «взорваться» к различным атомным ограничениям и стать не эквивалентны.

Ну, а зачем это компилятору?

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

#include <type_traits>
#include <iostream>
#include <concepts>

template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;

template< class T, class U >
concept my_same_as = SameHelper<T, U>;

// template< class T, class U >
// concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;

template< class T, class U> requires my_same_as<U, T>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

template< class T, class U> requires (my_same_as<T, U> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

В идеале, my_same_as<T, U> && std::integral<T>подгруппы my_same_as<U, T>; следовательно, компилятор должен выбрать вторую специализацию шаблона, кроме ... это не так: компилятор выдает ошибкуerror: call of overloaded 'foo(int, int)' is ambiguous .

Причиной этого является то, что, так как my_same_as<U, T>и my_same_as<T, U>не включает друг друга, my_same_as<T, U> && std::integral<T>иmy_same_as<U, T> становятся несопоставимыми (на частично упорядоченном множестве ограничений по отношению к подчинению).

Однако, если вы замените

template< class T, class U >
concept my_same_as = SameHelper<T, U>;

с

template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;

Код компилируется.

Рин Каенбё
источник
same_as <T, U> и same_as <U, T> также могут быть разными атомными ограничениями, но их результат будет одинаковым. Почему компилятор так заботится об определении same_as как двух разных атомарных ограничений, которые с логической точки зрения одинаковы?
user7769147
2
Компилятор должен рассматривать любые два выражения как разные для включения ограничений, но он может рассматривать аргументы для них очевидным образом. Так что мы не только нужны оба направления (так что это не имеет значения , в каком порядке они названы при сравнении ограничений), мы также должны SameHelper: она делает два использования из is_same_vпроистекают из того же выражения.
Дэвис Херринг
@ user7769147 Смотрите обновленный ответ.
Рин Каенбё
1
Кажется, что общепринятое мнение неверно в отношении понятия равенства. В отличие от шаблонов, где is_same<T, U>идентично is_same<U, T>, два атомных ограничения не считаются идентичными, если они также не сформированы из одного и того же выражения. Отсюда необходимость в обоих.
AndyG
Как насчет are_same_as? template<typename T, typename U0, typename... Un> concept are_same_as = SameAs<T, U0> && (SameAs<T, Un> && ...);потерпит неудачу в некоторых случаях. Например are_same_as<T, U, int>было бы эквивалентно, are_same_as<T, int, U>но неare_same_as<U, T, int>
user7769147
2

std::is_same определяется как истина тогда и только тогда, когда:

T и U называют один и тот же тип с одинаковыми cv-квалификациями

Насколько я знаю, стандарт не определяет значение слова «тот же тип», но в естественном языке и логике «то же самое» является отношением эквивалентности и, следовательно, является коммутативным.

Учитывая это предположение, которое я приписываю, is_same_v<T, U> && is_same_v<U, V>действительно будет излишним. Но same_­asне указано в терминах is_same_v; это только для экспозиции.

Явная проверка обоих позволяет реализовать для same-as-implудовлетворения, same_­asне будучи коммутативным. Указание этого способа точно описывает поведение концепции без ограничения ее реализации.

Почему именно этот подход был выбран вместо определения с точки зрения is_same_v, я не знаю. Преимущество выбранного подхода состоит в том, что оба определения не связаны между собой. Одно не зависит от другого.

eerorika
источник
2
Я согласен с вами, но этот последний аргумент немного натянут. Для меня это звучит так: «Эй, у меня есть этот компонент многократного использования, который говорит мне, являются ли два типа одинаковыми. Теперь у меня есть этот другой компонент, который должен знать, являются ли типы одинаковыми, но вместо того, чтобы повторно использовать мой предыдущий компонент Я просто создам специальное решение, специфичное для этого случая. Теперь я «отделил» парня, которому нужно определение равенства, от парня, у которого есть определение равенства. Ууу! »
Кассио Ренан
1
@ CassioRenan Конечно. Как я уже сказал, я не знаю почему, это просто лучшая аргументация, которую я мог бы придумать. Авторы могут иметь лучшее обоснование.
eerorika