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
は、T
がU
と同じかどうか、およびU
がT
と同じかどうかを確認しますか?冗長ではないですか?
興味深い質問です。私は最近、コンセプトに関する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>;
_
コードがコンパイルされます。
std::is_same
は、次の場合にのみtrueとして定義されます。
TとUが同じ型に同じcv-qualificationsで名前を付けている
私の知る限り、標準は「同じ型」の意味を定義していませんが、自然言語と論理では「同じ」は同値関係であり、交換可能です。
この仮定を踏まえると、is_same_v<T, U> && is_same_v<U, V>
は確かに冗長になります。ただし、same_as
はis_same_v
に関して指定されていません。それは説明のためだけです。
両方の明示的なチェックにより、same-as-impl
の実装は可換性なしにsame_as
を満たすことができます。この方法で指定すると、実装方法を制限することなく、概念の動作が正確に説明されます。
is_same_v
で指定するのではなく、このアプローチが選択された正確な理由はわかりません。選択したアプローチの利点は、2つの定義が分離されていることです。一方は他方に依存しません。