为何友元模板函数对struct与class的行为存在差异?
友元模板函数在struct与class中的行为差异问题
这段代码里定义了两个几乎完全一致的模板类型StringLiteral(struct)和StringLiteral2(class),唯一区别就是类型关键字不同。配套的用户定义字面量和operator==模板也一一对应。但编译时,GCC-15和Clang-20都接受"hello"_sl == "hello"_sl的断言,却拒绝"hello"_sl2 == "hello"_sl2,报错原因是无法访问StringLiteral2的私有成员value_。
#include <array> template<std::size_t tc_n> requires (tc_n > 0) struct StringLiteral final { std::array<char, tc_n> value{}; consteval StringLiteral(char const (&sl)[tc_n]) { for (auto i = std::size_t{}; i < tc_n; ++i) { this->value[i] = sl[i]; } } template<std::size_t tc_n1, std::size_t tc_n2> friend consteval bool operator==(StringLiteral<tc_n1> const& a, StringLiteral<tc_n2> const& b); }; template<std::size_t tc_n> requires (tc_n > 0) class StringLiteral2 final { std::array<char, tc_n> value_{}; public: consteval StringLiteral2(char const (&sl)[tc_n]) { for (auto i = std::size_t{}; i < tc_n; ++i) { this->value_[i] = sl[i]; } } template<std::size_t tc_n1, std::size_t tc_n2> friend consteval bool operator==(StringLiteral2<tc_n1> const& a, StringLiteral2<tc_n2> const& b); }; template<std::size_t tc_n1, std::size_t tc_n2> [[nodiscard]] consteval bool operator==(StringLiteral<tc_n1> const& a, StringLiteral<tc_n2> const& b) { return a.value == b.value; } template<std::size_t tc_n1, std::size_t tc_n2> [[nodiscard]] consteval bool operator==(StringLiteral2<tc_n1> const& a, StringLiteral2<tc_n2> const& b) { return a.value_ == b.value_; } template<StringLiteral tc_sl> constexpr auto operator""_sl() { return tc_sl; } template<StringLiteral2 tc_sl> constexpr auto operator""_sl2() { return tc_sl; } static_assert("hello"_sl == "hello"_sl); // ok static_assert("hello"_sl2 == "hello"_sl2); // error, why? int main() {}
问题根源
核心差异来自struct和class的默认成员访问权限,以及友元模板声明的匹配规则:
默认访问权限差异:
- struct的成员默认是
public,所以StringLiteral的value成员可以被任意外部函数访问,包括后面定义的operator==模板。哪怕这个模板不是友元,也能直接读取a.value完成比较,因此第一个static_assert正常通过。 - class的成员默认是
private,StringLiteral2的value_只能被友元或类内部成员访问。
- struct的成员默认是
友元模板的声明匹配问题:
在StringLiteral2内部声明的友元operator==模板,并没有和后面定义的外部operator==模板关联起来。因为在类内部声明友元模板时,如果没有提前在外部声明该模板,编译器会把这个友元声明当成一个全新的模板声明,而非引用后面定义的那个模板。
这就导致后面定义的operator==模板并没有获得StringLiteral2的友元权限,试图访问私有成员value_时自然会触发编译错误。
验证与解决
如果把StringLiteral的value改成private,它也会出现和StringLiteral2一样的编译错误,证明友元声明确实没生效。
要修复StringLiteral2的问题,只需在类定义之前提前声明operator==模板,让类内部的友元声明能正确引用到这个模板:
// 提前声明StringLiteral2模板 template<std::size_t tc_n> requires (tc_n > 0) class StringLiteral2; // 提前声明operator==模板 template<std::size_t tc_n1, std::size_t tc_n2> consteval bool operator==(StringLiteral2<tc_n1> const& a, StringLiteral2<tc_n2> const& b); // 然后再定义StringLiteral2类...
内容的提问来源于stack exchange,提问作者xmllmx
相关产品推荐
相关产品推荐

