Чому концепція same_as перевіряє рівність типу двічі?


19

Дивлячись на можливу реалізацію тієї самої концепції на 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? Хіба це не зайве?


Тільки тому, що SameHelper<T, U>може бути правдою, не означає, що SameHelper<U, T>може бути.
Якийсь програміст чувак

1
у цьому справа, якщо дорівнює b, b дорівнює a чи не так?
користувач7769147

@ 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, як два різних атомних обмеження, які з логічної точки зору однакові?
користувач7769147

2
Компілятор зобов’язаний розглянути будь-які два вирази як відмінні для обмеження обмеження, але він може розглядати аргументи до них очевидним чином. Так що ми не тільки потрібні обидва напрямки (так що це не має значення , в якому порядку вони названі при порівнянні обмежень), ми також повинні SameHelper: вона робить два використання з is_same_vвиникають через те ж вираження.
Девіс Оселедець

@ user7769147 Дивіться оновлену відповідь.
Rin Kaenbyou

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


2
Я згоден з вами, але цей останній аргумент трохи розтягнутий. Для мене це звучить так: "Гей, у мене є цей багаторазовий компонент, який підказує мені, чи є два типи однаковими. Тепер у мене є цей інший компонент, який повинен знати, чи типи однакові, але замість того, щоб повторно використовувати попередній компонент , Я просто створити спеціальне рішення, специфічне для даного випадку. Тепер я "розв'язав" хлопця, якому потрібно визначення рівності, від хлопця, який має визначення рівності. Так! "
Кассіо Ренан

1
@ CássioRenan Звичайно. Як я вже сказав, я не знаю чому, це просто найкращі міркування, які я міг би придумати. Автори, можливо, мають більше обґрунтування.
eerorika
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.