C ++ 20の概念:テンプレート引数が複数の概念に該当する場合、どのテンプレートの特殊化が選択されますか?


23

与えられた:

#include <concepts>
#include <iostream>

template<class T>
struct wrapper;

template<std::signed_integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "signed_integral" << std::endl;
    }
};

template<std::integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "integral" << std::endl;
    }
};

int main()
{
    wrapper<int> w;
    w.print(); // Output : signed_integral
    return 0;
}

上記のコードintから、std::integralとの両方のstd::signed_integral概念に適合します。

驚いたことに、これはGCCコンパイラとMSVCコンパイラの両方で「signed_integral」をコンパイルして出力します。「テンプレートの特殊化はすでに定義されています」というエラーが発生して失敗することを期待していました。

わかりました、それは合法で、十分に公正ですが、std::signed_integral代わりになぜ選ばれたのstd::integralですか?複数の概念がテンプレート引数に適格である場合に、どのテンプレート特殊化が選択されるかについて、標準で定義されているルールはありますか?


特にこの採用の初期段階では、コンパイラがそれを受け入れるという事実だけでは合法だとは思いません。
スラバ

それは彼らが直感的にお互いを包括して、概念は慎重に設計されている。この場合、@Slava
ギヨームRacicot

@GuillaumeRacicotそれは結構です、私はただ「コンパイラがそれを受け入れたのでそれは合法である」という結論は誤解を招くと言いましょうとコメントしました。これは合法ではないとは言いませんでした。
スラバ

回答:


14

これは、テンプレートの順序付けのように、概念が他の概念よりも専門化されているためです。これは、制約の部分的な順序付けと呼ばれます

概念の場合、同等の制約が含まれていると、それらは互いに包含し合います。たとえば、次はその方法std::integralstd::signed_integral実装です。

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T> //   v--------------v---- Using the contraint defined above
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

制約を正規化すると、コンパイラは制約式を次のように要約します。

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T>
concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;

この例では、完全にsigned_integral意味integralします。したがって、ある意味では、符号付き積分は積分よりも「制約が強い」のです。

標準では次のように記述しています。

[temp.func.order] / 2(強調鉱山):

部分的な順序付けでは、各テンプレートを順番に変換し(次の段落を参照)、関数タイプを使用してテンプレート引数の推定を実行することにより、2つの関数テンプレートのどちらがより特殊化されるかを選択します。控除プロセスでは、テンプレートの1つが他よりも特殊化されているかどうかを判断します。その場合、より特化したテンプレートは、部分的な注文プロセスで選択されたものです。 両方の演繹が成功した場合、部分的な順序付けは、[temp.constr.order]のルールで説明されているように、より制約されたテンプレートを選択します。

つまり、テンプレートに複数の可能な置換があり、両方が部分的な順序付けから選択された場合、最も制約のあるテンプレートが選択されます。

[temp.constr.order] / 1

制約Pを包含する制約Qすべての離接句のための場合にのみ、P Iの選言標準形でPをP iが、すべての論理積節包含するQのJの連言標準形でQを

  • 離接句P iは論理積節包含するQのjは原子の制約が存在する場合に限りPのIAにおけるP I原子制約が存在するため、QのJBにおけるQのjはそのようなことP IA包摂Q JBは、と

  • [temp.constr.atomic]で説明されているルールを使用してABが同一である場合に限り、アトミック制約Aは別のアトミック制約Bを包含します

これは、コンパイラーが制約を順序付けるために使用する包摂アルゴリズム、したがって概念について説明しています。


2
段落の途中で終了しているように見えます...
ShadowRanger

11

C ++ 20には、ある特定の制約されたエンティティが別のエンティティよりも「制約が強い」場合を決定するメカニズムがあります。これは簡単なことではありません。

これは、制約をそのアトミックコンポーネントに分解するという概念から始まります。これは、制約の正規化と呼ばれるプロセスです。ここに入るのは大きすぎて複雑すぎますが、基本的な考え方は、制約ではない各式は、概念ではないコンポーネントのサブ式に到達するまで、再帰的にアトミックな概念的な部分に分解されるということです。

では、integralとのsigned_integral概念どのように定義されているかを見てみましょう。

template<class T>
  concept integral = is_integral_v<T>;
template<class T>
  concept signed_­integral = integral<T> && is_signed_v<T>;

の分解integralはちょうどis_integral_vです。の分解はsigned_integralですis_integral_v && is_signed_v

ここで、制約の包摂という概念について説明します。少し複雑ですが、基本的な考え方は、C1の分解にC2のすべての部分式が含まれている場合、制約C1は制約C2を「包含する」と言われるということです。私たちは、それが見ることができるintegral包摂しませんsigned_integralが、signed_integral ありません包摂しintegral、それはすべてが含まれているので、integralありません。

次に、制約付きエンティティを注文します。

* D1とD2の両方が制約付き宣言であり、D1に関連付けられた制約がD2の制約を包含する場合、宣言D1は少なくとも宣言D2と同じように制約されます。または* D2には関連する制約がありません。

のでsigned_integral包摂integral<signed_integral> wrapperとして「制約少なくともとして」です<integral> wrapper。ただし、包摂は元に戻せないため、その逆は当てはまりません。

したがって、「より制約された」エンティティのルールに従って:

宣言D1は、D1が少なくともD2と同じくらい制約されており、D2が少なくともD1ほど制約されていない場合、別の宣言D2よりも制約されます。

<integral> wrapperは少なくともほど制約されていないため、<signed_integral> wrapper後者は前者よりも制約が強いと見なされます。

したがって、2つが両方とも当てはまる場合、より制約の多い宣言が優先されます。


制約の包摂のルールは、でない式に遭遇すると停止することに注意してくださいconcept。したがって、これを行った場合:

template<typename T>
constexpr bool my_is_integral_v = std::is_integral_v<T>;

template<typename T>
concept my_signed_integral = my_is_integral_v<T> && std::is_signed_v<T>;

この場合、包含しmy_signed_integral ませんstd::integralmy_is_integral_vはと同じようstd::is_integral_vに定義されていますが、それは概念ではないため、C ++の包摂規則はそれを調べてそれらが同じであると判断することはできません。

したがって、包摂規則は、アトミックな概念の操作から概念を構築することを奨励します。


3

Partial_ordering_of_constraints

制約Pは、PとQの原子制約のアイデンティティまで、PがQを意味することが証明できる場合、制約Qを包含すると言われます。

そして

包含関係は、制約の部分的な順序を定義します。これは、以下を決定するために使用されます。

  • オーバーロードの解決における非テンプレート関数の実行可能な最良の候補
  • オーバーロードセット内の非テンプレート関数のアドレス
  • テンプレートテンプレート引数に最適なもの
  • クラステンプレートの特殊化の部分的な順序付け
  • 関数テンプレートの部分的な順序付け

そして、コンセプトはコンセプトをstd::signed_integral包含するstd::integral<T>

template < class T >
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

したがって、コードは大丈夫std::signed_integralです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.