模板 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;
}
现在,我的问题是,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,这违反了模板的正常两阶段名称查找规则。
直到 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;
}
现在,我的问题是,C++20 的哪一部分允许我们这样做?为什么这在早期的 C++ 标准中是不可能的?
正如评论中指出的那样,clang 不接受演示中提供的这段代码,这表明这实际上可能是 gcc 中的一个错误。
我在 gcc 的 bugzilla
上提交了 bug reportGCC 有一个错误。
始终对出现在 <
之前的模板名称执行名称查找,即使所讨论的名称是在(友元、显式特化或显式实例化)声明中声明的名称也是如此。
因为好友声明中的名字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,这违反了模板的正常两阶段名称查找规则。