web-dev-qa-db-ja.com

same_asコンセプトが型の等価性を2回チェックするのはなぜですか?

https://en.cppreference.com/w/cpp/concepts/same_as で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コンセプトが必要なのかということです。 2つ目はsame_asは、TUと同じかどうか、およびUTと同じかどうかを確認しますか?冗長ではないですか?

19
user7769147

興味深い質問です。私は最近、コンセプトに関するAndrew Suttonの講演を見て、Q&Aセッションで誰かが次の質問をしました(次のリンクのタイムスタンプ): CppCon 2018:Andrew Sutton“ Concepts in 60:Everything you need知っていて、あなたがしていないことは何もない」

したがって、質問は次のようになります:_If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?_ Andrewは「はい」と答えましたが、コンパイラには、概念をアトミックな論理命題(_atomic constraints_ asアンドリューはその言葉を言いました)そしてそれらが同等であるかどうか調べます。

次に、cppreferenceが_std::same_as_について述べていることを見てください。

_std::same_as<T, U>_は_std::same_as<U, T>_を包含し、その逆も同様です。

それは基本的に「if-and-only-if」関係であり、それらは互いに意味します。 (論理的同等性)

私の推測では、ここではアトミック制約は_std::is_same_v<T, U>_です。コンパイラが_std::is_same_v_を処理する方法は、_std::is_same_v<T, U>_および_std::is_same_v<U, T>_を2つの異なる制約(それらは異なるエンティティです!)したがって、そのうちの1つだけを使用して_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>_を包含します。したがって、コンパイラーは2番目のテンプレート特殊化を選択する必要があります。ただし、...選択しない場合、コンパイラーはエラー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>;
_

コードがコンパイルされます。

15
Rin Kaenbyou

std::is_sameは、次の場合にのみtrueとして定義されます。

TとUが同じ型に同じcv-qualificationsで名前を付けている

私の知る限り、標準は「同じ型」の意味を定義していませんが、自然言語と論理では「同じ」は同値関係であり、交換可能です。

この仮定を踏まえると、is_same_v<T, U> && is_same_v<U, V>は確かに冗長になります。ただし、same_­asis_same_vに関して指定されていません。それは説明のためだけです。

両方の明示的なチェックにより、same-as-implの実装は可換性なしにsame_­asを満たすことができます。この方法で指定すると、実装方法を制限することなく、概念の動作が正確に説明されます。

is_same_vで指定するのではなく、このアプローチが選択された正確な理由はわかりません。選択したアプローチの利点は、2つの定義が分離されていることです。一方は他方に依存しません。

1
eerorika