Почему концепция same_as дважды проверяет равенство типов? - PullRequest
16 голосов
/ 22 октября 2019

Глядя на возможную реализацию концепции 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? Разве это не избыточно?

Ответы [ 2 ]

13 голосов
/ 22 октября 2019

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

Таким образом, вопрос сводится к следующему: 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> subumsstd::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>;

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

1 голос
/ 22 октября 2019

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, я не знаю. Преимущество выбранного подхода состоит в том, что оба определения не связаны между собой. Одно не зависит от другого.

...