C++17 和 C++20 在具有一元和二元运算符的模板友元函数中的区别

Difference of C++17 and C++20 in template friend function with unary and binary operators

我在 C++20 中有以下带有 clang++ -std=c++2a 的 MWE,其中我定义了 in-class 一元 - 运算符和 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).

当我删除 in-class 一元运算符时,或者当我通过 -std=c++2a.

使用 C++20 时,错误消失了

是什么导致了 C++17 中的这个问题,C++20 是如何解决这个问题的? 任何帮助将不胜感激!

这是由于名称查找在 class 上下文中进行的方式所致。在 friend 声明符中查找名称与在任何成员声明符中查找一样。适用于此处的相关查找规则是:

In all the cases listed in [basic.lookup.unqual], the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.

A name used in the definition of a class X outside of a complete-class context ([class.mem]) of X shall be declared in one of the following ways:

  • before its use in class X or be a member of a base class of X ([class.member.lookup]), or
  • [...]
  • if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the definition of class X in namespace N or in one of N's enclosing namespaces.

这意味着:

  1. 名称首先在 class 范围内查找成员名称;

  2. 如果之前的查找失败,将在封闭的命名空间范围内查找名称。

当编译器在友元声明中找到名称 operator- 时,它会在 class 上下文中执行名称查找(不完整)。它找到一元减号运算符并停在那里。

之后,编译器应用以下规则来确定名称 operator - 是否可以是模板名称 C++17/[temp.name]/3

After name lookup finds that a name is a template-name or that an operator-function-id or a literal-operator-id refers to a set of overloaded functions any member of which is a function template, if this is followed by a <, the < is always taken as the delimiter of a template-argument-list and never as the less-than operator. [...]

查找未找到任何模板,因此在朋友声明中 operator - 不应该命名模板。编译器准确地抱怨这个名字后面的 < 标记,它不应该在那里。

新的 C++20 规则使编译器更倾向于解释名称指的是模板,C++20 standard/[temp.names]/2:

A name is considered to refer to a template if name lookup finds a template-name or an overload set that contains a function template. A name is also considered to refer to a template if it is an unqualified-id followed by a < and name lookup either finds one or more functions or finds nothing.

class vec 范围内的名称查找找到一个函数名称,这个名称后跟一个 < 字符,所以这个名称指的是一个模板。