C ++ 20テンプレートクラスのクラス外定義


12

C ++のC ++ 20標準までは、テンプレートクラスのいくつかのプライベートメンバーを使用するクラス外の演算子を定義する場合、次のような構成を使用していました。

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

ただし、C ++ 20以降では、クラス外の宣言を省略できるため、前方宣言も省略できるため、次のようにして対処できます。

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

さて、私の質問は、C ++ 20のどの部分がそれを可能にするのですか?そして、なぜ以前のC ++標準ではこれが可能でなかったのですか?


コメントで指摘されたように、clangはデモで提示されたこのコードを受け入れません。これは、これが実際にはgccのバグである可能性を示唆しています。

gccのバグジラに関するバグレポートを提出しました


2
私は個人的にはクラス定義を好み、テンプレート機能(および「問題」の控除(とは一致しません"c string" == Foo<std::string>("foo")))を避けています。
Jarod42

@ Jarod42完全に同意します。クラス内の定義も好みます。C ++ 20では、クラスのouf-ofを定義するときに関数のシグネチャを3回繰り返さないようにできることを知って驚いただけです。これは、実装が非表示の.inlファイルにあるパブリックAPIで役立つ場合があります。
ProXicT

それが不可能だとは気づかなかった。これまで問題なく使用したのはなぜですか?
ALX23z

1
うーん、でtemp.friend、あまり特にない、変わっていない1.3この挙動の原因であるべきです。clangはコードを受け入れないため、私はgccにバグがあることに傾倒しています。
n314159

@ ALX23zクラスがテンプレート化されていない場合、クラス外宣言なしで機能します。
ProXicT

回答:


2

GCCにはバグがあります。

<問題の名前が(フレンド、明示的な特殊化、または明示的なインスタンス化)宣言で宣言されている名前であっても、名前の検索は常に、の前にあるテンプレート名に対して実行されます。

名前なのでoperator==friend宣言では、非修飾名であり、テンプレートに、検索名の対象となり、二相名のルックアップ規則が適用されます。このコンテキストでoperator==は、は依存名ではないため(関数呼び出しの一部ではないため、ADLは適用されません)、名前が検索され、出現した場所でバインドされます([temp.nondep]の段落1を参照)。この名前の検索での宣言が見つからないため、例の形式が正しくありませんoperator==

P0846R0により、GCCはこれをC ++ 20モードで受け入れます。これにより、たとえばoperator==<T>(a, b)、テンプレートとしての事前の宣言operator==が表示されていない場合でも、テンプレートで使用できます。

これはさらに興味深いテストケースです:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

-DWRONG_DECL、GCCとClangは、このプログラムが不正な形式であることに同意します。フレンド定義#2の非修飾ルックアップは、テンプレート定義のコンテキストで、インスタンス化されたのフレンドと一致しない宣言#1を見つけますFoo<int>。宣言#3は考慮されていません。テンプレートでの修飾されていないルックアップでは検出されないためです。

を使用すると-UWRONG_DECL、GCC(C ++ 17以前)とClangは、このプログラムが別の理由で不正な形式であることに同意しますoperator==。2行目での修飾されていない検索では何も見つかりません。

しかし-UWRONG_DECL、C ++ 20モードのGCCはoperator==、#2の非修飾ルックアップが(おそらくP0846R0が原因で)失敗しても大丈夫であると判断し、テンプレートのインスタンス化コンテキストからルックアップをやり直すように見えます。テンプレートの通常の2フェーズの名前ルックアップルールの違反。


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