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

