web-dev-qa-db-ja.com

単項演算子と二項演算子を使用したテンプレートフレンド関数のC ++ 17とC ++ 20の違い

私は次のMWEをC++ 20で_clang++ -std=c++2a_とともに使用し、クラスの単項_-_演算子とfriend- edバイナリ_-_演算子を定義しました。

_template<typename T>
class vec;

template<typename T>
vec<T> operator-(const vec<T>&, const vec<T>&);

template<typename T>
class vec {
public:
    vec() {}
    vec operator-() const { return vec(); }
    friend vec operator-<>(const vec&, const vec&);
};

template<typename T>
vec<T> operator-(const vec<T>& lhs, const vec<T>& rhs) { return vec<T>(); }

int main()
{
    vec<int> v;
    return 0;
}
_

ただし、これによりC++ 17では次のエラーが発生します。

_main.cpp:12:16: error: friends can only be classes or functions
    friend vec operator-<>(const vec&, const vec&);
               ^
main.cpp:12:25: error: expected ';' at end of declaration list
    friend vec operator-<>(const vec&, const vec&);
                        ^
                        ;
_

Apple clang version 11.0.3 (clang-1103.0.32.59)を使用します。

クラス内の単項演算子を削除するか、_-std=c++2a_を介してC++ 20を使用すると、エラーが消えます。

C++ 17でこの問題の原因は何ですか?C++ 20はこの問題をどのように解決しますか?どんな助けでも大歓迎です!

8
Jay Lee

これは、クラスコンテキスト内で名前の検索が行われる方法が原因です。フレンド宣言子内の名前を検索すると、他のメンバー宣言子と同様に検索されます。ここで適用される関連する検索ルールは次のとおりです。

[basic.lookup.unqual]にリストされているすべてのケースで、スコープはそれぞれのカテゴリのそれぞれにリストされている順序で宣言を検索します。名前の宣言は、名前の宣言が見つかるとすぐに終了します。宣言が見つからない場合、プログラムは不正な形式です。

Xの完全なクラスコンテキスト([class.mem])の外のクラスXの定義で使用される名前は、次のいずれかの方法で宣言されます。

  • クラスXで使用する前、またはXの基本クラスのメンバーになる([class.member.lookup])、または
  • [...]
  • xが名前空間Nのメンバーであるか、Nのメンバーであるクラスのネストされたクラスであるか、またはローカルクラスまたはNのメンバーである関数のローカルクラス内のネストされたクラスである場合、定義の前名前空間NまたはNを囲む名前空間の1つにあるクラスXの。

つまり:

  1. 名前は、最初にクラススコープでメンバー名を検索します。

  2. この以前のルックアップが失敗した場合、名前はそれを囲む名前空間スコープでルックアップされます。

コンパイラがフレンド宣言で名前operator-を見つけると、クラスコンテキストで名前ルックアップを実行します(不完全)。単項マイナス演算子を見つけてそこで停止します。

その後、コンパイラーは次のルールを適用して、名前operator -をテンプレート名にできるかどうかを判断します C++ 17/[temp.name]/

名前の検索により、名前がテンプレート名であるか、またはoperator-function-idまたはliteral-operator-idが、関数テンプレートであるオーバーロードされた関数のセットを参照していることが検出された後、< 、<は常にテンプレート引数リストの区切り文字として使用され、小なり演算子としては使用されません。 [...]

ルックアップでテンプレートが見つからなかったため、フレンド宣言operator -内ではテンプレートに名前を付けることは想定されていません。コンパイラは、この名前に続く<トークンで正確に文句を言います。

新しいC++ 20ルールにより、コンパイラーは名前がテンプレートを参照していると解釈する傾向が強くなります C++ 20 standard/[temp.names]/2

名前ルックアップがテンプレート名または関数テンプレートを含むオーバーロードセットを見つけた場合、名前はテンプレートを参照していると見なされます。名前が非修飾IDであり、その後に<およびの名前を検索すると、1つ以上の関数が検出されるか、何も検出されない場合にも、テンプレートを参照すると見なされます

クラスvecスコープでの名前の検索は関数名を見つけ、この名前の後に<文字が続くため、この名前はテンプレートを参照します。

1
Oliv