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

okwaves2024-01-25  8

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 に等しいということですよね?

– user7769147

2019 年 10 月 22 日 17:06

1

@user7769147 はい、これがその関係を定義しています。

– フランçワ アンドリュー

2019 年 10 月 22 日 17:12

5

うーん、std::is_same のドキュメントには、「可換性が満たされる、つまり任意の 2 つの型 T と U について、is_same の場合およびその場合に限り、is_same::value == true」とさえ書かれています。 ::値 == true。」これは、この二重チェックが必要ないことを意味します

– ケビン

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 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 を実行します。および 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 を包含します。したがって、コンパイラは 2 番目のテンプレート特殊化を選択する必要があります。ただし、そうではありません: コンパイラはエラー error: オーバーロードされた 'foo(int, int)' の呼び出しがあいまいです。

この背後にある理由は、my_same_as<U, T> がそして私は<T、U>と同じです。お互いを包摂せず、 と同じです&& std::integral;そして私は<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>;

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

2019 年 10 月 22 日 19:47 に回答

ph3rin

ph3rin

4,596

1

金バッジ 1 個

19 個

銀バッジ 19 個

42 個

銅バッジ 42 個

7

と同じ と同じです。異なる原子制約を使用することもできますが、結果は同じになります。なぜコンパイラーは、論理的な観点からは同じである 2 つの異なるアトミック制約として Same_as を定義することをそれほど気にするのでしょうか?

– user7769147

2019 年 11 月 4 日 15:03

5

コンパイラは次の点を考慮する必要があります。r 任意の 2 つの式は制約包含に関しては別個のものとして扱われますが、明白な方法でそれらに対する引数を考慮することができます。したがって、両方の方向が必要なだけでなく (制約を比較するときに名前の順序が問題にならないように)、SameHelper も必要です。これにより、is_same_v の 2 つの使用が同じ式から派生します。

– デイビス ヘリング

2019 年 11 月 4 日 20:07

2

概念の平等に関する従来の通念は間違っているようです。 is_same であるテンプレートとは異なります。 is_same と同一であり、2 つのアトミック const雨は、同じ式から形成されない限り、同一とはみなされません。したがって、両方が必要になります。

– AndyG

2019 年 11 月 5 日 13:08

are_same_as についてはどうですか?テンプレート<タイプ名 T、タイプ名 U0、タイプ名... Un>コンセプト are_same_as = SameAs && (同じ && ...);場合によっては失敗するでしょう。たとえば、are_same_as のようになります。 are_same_as<T, int, U> と同等になります。ただし、<U, T, int> と同じではありません

– user7769147

2019 年 11 月 5 日 16:27

さらに、概念はそれ自体を再帰的に参照できないため、このテンプレートは となります。コンセプト are_same_as = SameAs && (同じ && ...) && (sizeof...(Un) == 0 || are_same_as);許可されません

– user7769147

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>;
同じ Same を包含します。およびその逆。

矛盾しているように思えます。概念の定義だけでは、 同じの場合Same を包含します。その逆も同様です。段落 1 は、 包摂関係が述べられているが、一般の読者にはそう見える 誤って注釈が付けられたメモである可能性があります。説明するメモを追加する必要があります ここで実際に何が起こっているのか、またはそのようにコンセプトを定義する それは、指定された包含関係を自然に提供するということです。

次のことを考慮すると、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>;
[注: 同じ Same を包含します。およびその逆。 — エンディングノート]

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 は #2、C1 の制約を包含しますが、その逆はありません。

ただし、[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>;

と同じは、依然として、same_as を包含します。およびその逆;ただし、これはまったく簡単なわけではありません。

したがって、「実際の内容を説明するメモを追加する」オプションを選択する代わりに、ここで起こっていることだ」 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> があるためです。明らかに対称ではありませんか?そして、それを「対称概念」のような言語で綴る良い方法はありません。キーワード。

– NoSenseetAl

2021 年 2 月 3 日 19:43

提案された修正は、元の実装はコンパイラの魔法によってのみ機能する ([temp.constr.order] がそう義務付けているからではない) と言っていたと思います。

– ph3rin

2021 年 3 月 8 日 4:35

" .. ウサギの穴に行くことができます ... 古い実装でもそれを示すことができます ... と同じですまだサムを包含するだろうe_as" 「標準を修正して機能させることができる」という意味ですか、それとも「すでに機能するはずです」という意味ですか?既存のコンパイラでは動作しないようです: gcc.godbolt.org/z/q5hq1b3ME

– ホーリーブラックキャット

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

総合生活情報サイト - OKWAVES
総合生活情報サイト - OKWAVES
生活総合情報サイトokwaves(オールアバウト)。その道のプロ(専門家)が、日常生活をより豊かに快適にするノウハウから業界の最新動向、読み物コラムまで、多彩なコンテンツを発信。