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

C++模板String类隐藏友元operator+兼容字面量的简化实现

编译期字符串类拼接问题解决方案

核心问题描述

我希望实现一个编译期字符串类String,现有模板代码可通过String("hello")创建String<5>实例(自动移除末尾的零)。为该类添加用于拼接的隐藏友元operator+后,无法兼容String与字符串字面量的拼接:

  • "hello"+String("!")会错误推导长度
  • String("hello")+"!"则直接编译失败

非模板类无需额外重载即可实现该功能,是否存在比添加两个额外operator+重载更简洁的实现方式?

附加问题

能否编写一个隐藏友元concat函数,可拼接任意数量的String实例与字符串字面量(要求至少一个参数为String)?

注:推测"hello"+String("!")错误的原因是ADL仅找到String<1>中的operator+,导致模板参数S和O均被推导为1。


一、简洁解决字符串字面量与String拼接问题

问题根源在于隐藏友元的模板参数推导限制:当仅为String<N>定义接受String<O>的友元operator+时,ADL无法匹配字符串字面量类型,反向拼接时还会因为绑定到String<1>导致参数推导错误。

最优简洁实现:双模板友元覆盖所有拼接场景

通过定义两个模板化的隐藏友元operator+,分别处理左操作数为String和右操作数为String的情况,内部用if constexpr区分另一参数是String还是字符串字面量,无需额外独立重载:

#include <charconv>

template <size_t N>
struct String {
    static constexpr size_t size = N;
    const char data[N + 1]; // 保留末尾'\0'方便编译期处理

    // 从字符串字面量初始化,自动推导长度(移除末尾零)
    template <size_t M>
    constexpr String(const char (&s)[M]) : data{} {
        static_assert(M > 0, "Empty string literal is not allowed");
        for (size_t i = 0; i < N; ++i) {
            data[i] = s[i];
        }
        data[N] = '\0';
    }

    // 处理 String + 任意类型(String或字面量)
    template <typename T>
    friend constexpr auto operator+(const String& lhs, const T& rhs) {
        if constexpr (requires { rhs.size; }) { // 判断是否为String类型
            constexpr size_t total = N + T::size;
            char result[total + 1]{};
            // 复制左操作数
            for (size_t i = 0; i < N; ++i) result[i] = lhs.data[i];
            // 复制右操作数
            for (size_t i = 0; i < T::size; ++i) result[N + i] = rhs.data[i];
            return String<total>(result);
        } else { // 处理字符串字面量
            constexpr size_t literal_len = std::char_traits<char>::length(rhs);
            constexpr size_t total = N + literal_len;
            char result[total + 1]{};
            for (size_t i = 0; i < N; ++i) result[i] = lhs.data[i];
            for (size_t i = 0; i < literal_len; ++i) result[N + i] = rhs[i];
            return String<total>(result);
        }
    }

    // 处理 任意类型(字面量或String) + String
    template <typename T>
    friend constexpr auto operator+(const T& lhs, const String& rhs) {
        if constexpr (requires { lhs.size; }) {
            constexpr size_t total = T::size + N;
            char result[total + 1]{};
            for (size_t i = 0; i < T::size; ++i) result[i] = lhs.data[i];
            for (size_t i = 0; i < N; ++i) result[T::size + i] = rhs.data[i];
            return String<total>(result);
        } else {
            constexpr size_t literal_len = std::char_traits<char>::length(lhs);
            constexpr size_t total = literal_len + N;
            char result[total + 1]{};
            for (size_t i = 0; i < literal_len; ++i) result[i] = lhs[i];
            for (size_t i = 0; i < N; ++i) result[literal_len + i] = rhs.data[i];
            return String<total>(result);
        }
    }
};

// 推导指南:让String("hello")自动推导为String<5>
template <size_t M>
String(const char (&)[M]) -> String<M - 1>;

这个实现仅用两个模板友元就覆盖了String+String、String+字面量、字面量+String三种拼接场景,避免了冗余重载,同时利用编译期判断保证类型安全。


二、可变参数concat函数实现

要支持任意数量String与字面量的拼接(至少一个参数为String),可以通过可变模板参数+编译期长度计算实现隐藏友元concat,无需递归展开,直接一次性构造结果:

#include <type_traits>
#include <charconv>

template <size_t N>
struct String {
    static constexpr size_t size = N;
    const char data[N + 1];

    template <size_t M>
    constexpr String(const char (&s)[M]) : data{} {
        static_assert(M > 0);
        for (size_t i = 0; i < N; ++i) data[i] = s[i];
        data[N] = '\0';
    }

    // 编译期计算单个参数的有效长度
    template <typename T>
    static constexpr size_t get_element_length(const T&) {
        if constexpr (requires { T::size; }) {
            return T::size;
        } else {
            return std::char_traits<char>::length(T{});
        }
    }

    // 编译期计算所有参数的总长度
    template <typename... Args>
    static constexpr size_t total_length(const Args&... args) {
        return (get_element_length(args) + ...);
    }

    // 复制单个参数到结果数组
    template <typename T>
    static constexpr void copy_element(char* dest, const T& src) {
        if constexpr (requires { src.size; }) {
            for (size_t i = 0; i < src.size; ++i) {
                dest[i] = src.data[i];
            }
        } else {
            constexpr size_t len = std::char_traits<char>::length(src);
            for (size_t i = 0; i < len; ++i) {
                dest[i] = src[i];
            }
        }
    }

    // 递归复制所有参数到结果数组
    template <typename T, typename... Args>
    static constexpr void copy_all(char* dest, const T& first, const Args&... rest) {
        copy_element(dest, first);
        if constexpr (sizeof...(rest) > 0) {
            copy_all(dest + get_element_length(first), rest...);
        }
    }

    // 隐藏友元concat:支持任意数量参数,至少一个为String
    template <typename... Args>
        requires (std::disjunction_v<std::is_same<std::decay_t<Args>, String<auto>>...>)
    friend constexpr auto concat(const Args&... args) {
        constexpr size_t total_len = total_length(args...);
        char result[total_len + 1]{};
        copy_all(result, args...);
        result[total_len] = '\0';
        return String<total_len>(result);
    }
};

template <size_t M>
String(const char (&)[M]) -> String<M - 1>;

使用示例

constexpr auto s1 = String("hello");
constexpr auto s2 = concat(s1, " ", "world", String("!")); // 得到String<12>,内容为"hello world!"
constexpr auto s3 = concat("foo", s1, "bar"); // 得到String<11>,内容为"foohellobar"

内容的提问来源于stack exchange,提问作者geza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:55:59