You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何友元模板函数对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的默认成员访问权限,以及友元模板声明的匹配规则:

  1. 默认访问权限差异:

    • struct的成员默认是public,所以StringLiteral的value成员可以被任意外部函数访问,包括后面定义的operator==模板。哪怕这个模板不是友元,也能直接读取a.value完成比较,因此第一个static_assert正常通过。
    • class的成员默认是private,StringLiteral2的value_只能被友元或类内部成员访问。
  2. 友元模板的声明匹配问题:
    在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:53:12