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 と同じかどうかをチェックするのかということです。冗長ではないでしょうか?
SameHelper<T, U> という理由だけで、 true である可能性は、SameHelper を意味するわけではありません。そうかもしれません。
– プログラマーの男2019 年 10 月 22 日 17:05
2
重要なのは、a が b に等しい場合、b は a に等しいということですよね?
– user77691472019 年 10 月 22 日 17:06
1
@user7769147 はい、これがその関係を定義しています。
– フランçワ アンドリュー2019 年 10 月 22 日 17:12
5
うーん、std::is_same のドキュメントには、「可換性が満たされる、つまり任意の 2 つの型 T と U について、is_same
2019 年 10 月 22 日 17:13
2
いいえ、これは間違っています。std::is_same は次のように述べています: 条件が成立する場合に限り、2 つの型は可換です。必ずしもそうとは限りません。しかし、2 つの非可換型の例は見つかりませんでした。
– ネマニャ・ボリッチ2019 年 10 月 22 日 17:36
興味深い質問です。最近、コンセプトに関する Andrew Sutton の講演を視聴しましたが、Q&A セッションで誰かが次の質問をしました (タイムスタンプは次のリンクにあります)。 CppCon 2018: アンドリュー サットン「60 年の概念: 知っておくべきことはすべて、知らないことは何もない」
質問は要約すると次のようになります。もし私にコンセプトがあるとしたら、A&&と言うのはB&amp;&amp; C、別の人は C &&; と言います。 B&amp;&amp; A、それらは同等でしょうか? Andrew は「はい」と答えましたが、コンパイラには、概念をアトミック論理命題 (アンドリューの言葉ではアトミック制約) に分解し、それらが同等かどうかをチェックする内部メソッド (ユーザーには透過的) がいくつかあるという事実を指摘しました。
次に、cppreference が std::same_as について述べていることを見てみましょう。
std::same_as
これは基本的に「if-and-only-if」です。関係:それらはお互いを暗示します。 (論理的等価性)
私の推測では、ここでのアトミック制約は std::is_same_v<T, U> であると考えられます。コンパイラーが std::is_same_v を処理する方法により、コンパイラーは std::is_same_v<T, U& と考えてしまう可能性があります。gt;および 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
では、コンパイラはなぜそれを気にするのでしょうか?
次の例を考えてみましょう。
#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> 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
この背後にある理由は、my_same_as<U, T> がそして私は<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>;
コードがコンパイルされます。
2019 年 10 月 22 日 19:47 に回答
ph3rinph3rin
4,596
1
金バッジ 1 個
19 個
銀バッジ 19 個
42 個
銅バッジ 42 個
7
2019 年 11 月 4 日 15:03
5
コンパイラは次の点を考慮する必要があります。r 任意の 2 つの式は制約包含に関しては別個のものとして扱われますが、明白な方法でそれらに対する引数を考慮することができます。したがって、両方の方向が必要なだけでなく (制約を比較するときに名前の順序が問題にならないように)、SameHelper も必要です。これにより、is_same_v の 2 つの使用が同じ式から派生します。
– デイビス ヘリング2019 年 11 月 4 日 20:07
2
概念の平等に関する従来の通念は間違っているようです。 is_same
2019 年 11 月 5 日 13:08
are_same_as についてはどうですか?テンプレート<タイプ名 T、タイプ名 U0、タイプ名... Un>コンセプト are_same_as = SameAs
2019 年 11 月 5 日 16:27
さらに、概念はそれ自体を再帰的に参照できないため、このテンプレートは
2019 年 11 月 5 日 16:36
[concept.same] は、LWG 問題 3182 の一部として変更されました (コンセプト Same の名前が is_same a に変更される前)s per P1754R1) [私の強調]:
3182. 同じものの仕様がより明確になる可能性がある セクション: 18.4.2 [concept.same] ステータス: WP [...]ディスカッション:
18.4.2 の同じコンセプトの仕様 [concept.same]:
template<class T, class U>
concept Same = is_same_v<T, U>;
同じ矛盾しているように思えます。概念の定義だけでは、
同じ
次のことを考慮すると、re は対称包摂イディオムの直接的なライブラリ実装ですが、後者のオプションの方が望ましいと思われます。
[...]
提案された解決策:
この文言は N4791 に関連したものです。
18.4.2 [concept.same] を次のように変更します。
template<class T, class U>
concept same-impl = // exposition only
is_same_v<T, U>;
template<class T, class U>
concept Same = is_same_v<T, U>same-impl<T, U> && same-impl<U, T>;
[注: 同じOP の 2 番目の質問に取り組み始めます (最初の質問に対する答えはそこから続くため)。
OP: 2 つ目は、なぜ Same_as が T が U と同じか、U が T と同じかをチェックするのかということです。冗長ではないでしょうか?
上で強調した最後の部分によると:
[...] 対称包含イディオムの簡単なライブラリ実装があることを考えると、後者のオプションの方が望ましいと思われます。
CWG 3182 に対する決議は次のとおりです。ライブラリの仕様を再定義して、特に 2 つの対称制約を使用して、(意味的に) 自然な方法で 2 つの間の包含関係 (「対称包含イディオム」) を満たすようにします。
余談ですが (ただし、OP の最初の質問への回答に関連します)、これは、[temp.constr.order]、特に [temp.constr.order]/1 および [temp.constr.order] に従って、制約による部分的な順序付けにとって重要になる可能性があります。 constr.order]/3
/1 制約 P は、[...] [ 例: A と B をアトミック制約とする。制約 A ∧ B は A を包含しますが、A は A ∧ B を包含しません。制約 A は A ∨ B を包含しますが、A ∨ B は A を包含しません。また、すべての制約はそれ自体を包含することに注意してください。 — 例の終了 ]
/3 宣言 D1 は少なくとも制約ですd を宣言として D2 if
(3.1) D1 と D2 は両方とも制約付き宣言であり、D1 に関連付けられた制約は D2 の制約を包含します。または (3.2) D2 には関連する制約がありません。次の例では次のようになります。
#include <iostream>
template <typename T> concept C1 = true;
template <typename T> concept C2 = true;
template <typename T> requires C1<T> && C2<T> // #1
void f() { std::cout << "C1 && C2"; }
template <typename T> requires C1<T> // #2
void f() { std::cout << "C1"; }
たとえば、f<int>() の呼び出しは、#1、C1<T> の制約があるため、曖昧ではありません (#1 が呼び出されます)。 && C2
ただし、[temp.constr.order] と [temp.constr.atomic] のウサギの穴を調べて、same_as の古い実装でもそれを示すことができます。
// old impl.; was named Same back then
template<typename T, typename U>
concept same_as = is_same_v<T, U>;
したがって、「実際の内容を説明するメモを追加する」オプションを選択する代わりに、ここで起こっていることだ」 LWG 3182 を解決するために、[concept.same] は代わりに、「カジュアルな読者」にとってより明確な意味を持つ形式で定義されるようにライブラリ実装を変更しました。
// A and B are concepts
concept same_as = A ^ B
上記の (接線) 部分のように、same_as は概念 A と B の両方を単独で包含しますが、A と B を単独で包含することは、same_as を包含しないことにも注意してください。
OP: 最初の質問は、なぜ SameHelper の概念が不要になるのかということです。
temp.constr.order]/1 に従って、概念のみを包含できます。したがって、is_same 変換特性 (概念ではない) が直接使用されていた概念の古い実装では、特性自体は包含ルールに該当しませんでした。実装の意味は次のとおりです。
template< class T, class U >
concept same_as = std::is_same_v<T, U> && std::is_same_v<U, T>
本当に冗長なものが含まれますr.h.s. && の場合、型特性は型特性を包含できないためです。 LWG 3182 が解決され、上記のような包摂関係を意味論的に示すことが意図されていたとき、包摂に重点を置くための中間概念が追加されました。
3
つまり、コンパイラは is_same が対称であることを知らない/想定できないということになります。たとえば has_greater_sizeof<A,B> があるためです。明らかに対称ではありませんか?そして、それを「対称概念」のような言語で綴る良い方法はありません。キーワード。
– NoSenseetAl2021 年 2 月 3 日 19:43
提案された修正は、元の実装はコンパイラの魔法によってのみ機能する ([temp.constr.order] がそう義務付けているからではない) と言っていたと思います。
– ph3rin2021 年 3 月 8 日 4:35
" .. ウサギの穴に行くことができます ... 古い実装でもそれを示すことができます ...
2022 年 2 月 28 日 20:21
std::is_same は、次の場合にのみ true として定義されます。
T と U は、同じ cv-qualification を持つ同じ型に名前を付けます
私の知る限り、標準では「同じ型」の意味は定義されていませんが、自然言語や論理では「同じ」は同値関係であり、可換です。
私が帰しているこの仮定を考慮すると、is_same_v<T, U> && is_same_v確かに冗長になります。ただし、same_as は is_same_v に関して指定されていません。これは説明のみを目的としています。
両方の明示的なチェックにより、same-as-impl の実装が可換的になることなく Same_as を満たすことが可能になります。このように指定すると、実装方法を制限することなく、コンセプトがどのように動作するかを正確に説明できます。
is_same_v に関して指定する代わりにこのアプローチが選択された正確な理由はわかりません。選択したアプローチの利点は、おそらく 2 つの定義が分離されていることです。一方が他方に依存することはありません。
2
3
私もあなたに同意しますが、この最後の議論は少し無理があります。私にとって、それは次のように聞こえます。「ねえ、2 つの型が同じかどうかを知らせる再利用可能なコンポーネントがあります。」これで、型が同じかどうかを知る必要がある別のコンポーネントができました。ただし、前のコンポーネントを再利用する代わりに、このケースに固有のアドホック ソリューションを作成するだけです。今、私は「切り離しました」。平等の定義を持っている男から平等の定義を必要とする男。やったー!」
– 本物のミーアキャットではありません2019 年 10 月 22 日 18:54
1
@CássioRenan そうですね。先ほども言いましたが、理由はわかりませんが、それが私が思いつく最善の理由です。著者らはもっと良い理論的根拠を持っているかもしれません。
– エロリカ2019 年 10 月 22 日 19:12