模板 class 中的 C++20 超出 class 定义

C++20 out-of-class definition in a template class

直到 C++ 的 C++20 标准,当我们想要定义一个使用模板 class 的一些私有成员的 out-of-class 运算符时,我们会使用构造类似于:

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 开始,我们可以省略 out-of-class 声明,因此也可以省略前向声明,所以我们可以只用:

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 的 bugzilla

上提交了 bug report

GCC 有一个错误。

始终对出现在 < 之前的模板名称执行名称查找,即使所讨论的名称是在(友元、显式特化或显式实例化)声明中声明的名称也是如此。

因为好友声明中的名字operator==是一个不合格的名字,在模板中进行名字查找,所以应用两阶段名字查找规则。在此上下文中,operator== 不是从属名称(它不是函数调用的一部分,因此 ADL 不适用),因此查找名称并在它出现的位置进行绑定(参见 [temp.nondep] 第 1 段)。您的示例格式错误,因为此名称查找未找到 operator==.

的声明

我希望 GCC 在 C++20 模式下接受这个,因为 P0846R0,它允许(例如)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,但它没有t 匹配 Foo<int> 的实例化好友。甚至不考虑声明 #3,因为模板中的非限定查找找不到它。

对于 -UWRONG_DECL,GCC(在 C++17 及更早版本中)和 Clang 一致认为该程序格式错误的原因不同:第 2 行 operator== 的不合格查找发现没什么。

但是对于 -UWRONG_DECL,C++20 模式下的 GCC 似乎认为在 #2 中对 operator== 的非限定查找失败是可以的(可能是由于 P0846R0),然后似乎从模板实例化上下文重新查找,现在发现#3,这违反了模板的正常两阶段名称查找规则。