Ради оптимизации на обычных строковых литералах (первый юнит-тест) я пошёл на неочевидную работу на константных буферах (второй юнит-тест). Для краткости версии для wchar_t опущены. Система тестирования, если что, Google Test. Строки бывают страшные, иногда дважды заэкранированные (сначала в JS, потом в Си), и даже добавлять к ним «sv» боязно.
Собственно, вопрос: как в шаблоне отличить строковый литерал от константного буфера, чтобы применить к ним разные len и append?
namespace str {
namespace detail {
inline constexpr size_t len(char) { return 1; }
inline size_t len(const char* s) { return strlen(s); }
inline size_t len(const std::string& s) { return s.length(); }
inline constexpr size_t len(std::string_view s) { return s.length(); }
template <size_t N> inline constexpr size_t arrLen(const char (&)[N])
{ if constexpr (N == 0) return 0; else return N - 1; }
template <size_t N> inline constexpr size_t arrLen(char* (&s)[N]) { return strnlen(s, N); }
}
template <class T>
constexpr inline size_t len(T&& s) {
if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
return detail::arrLen(s);
} else {
return detail::len(s);
}
}
// append - 0 strings
inline void append(std::string&) {}
// append - 1 string
namespace detail {
inline void append1(std::string& r, char s) { r.push_back(s); }
void append1(std::string&, wchar_t) = delete;
inline void append1(std::string& r, const char* s) { r.append(s); }
inline void append1(std::string& r, const std::string& s) { r.append(s); }
inline void append1(std::string& r, std::string_view s) { r.append(s); }
template <size_t N> inline void arrAppend(std::string& r, const char (&s)[N])
{ if constexpr (N > 0) r.append(s, N - 1); }
template <size_t N> inline void arrAppend(std::string& r, char (&s)[N])
{ r.append(s, strnlen(s, N)); }
template <class R, class T>
inline void append2(R&& r, T&& s) {
if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
arrAppend(r, s);
} else {
append1(r, s);
}
}
}
template <class T>
inline void append (std::string& r, T&& s) { detail::append2(r, s); }
// append - 2+ strings
template <class T, class... Args>
inline void append(std::string& r, T&& s, Args&& ... args) {
r.reserve(r.length() + len(s) + (len(args) + ...));
detail::append2(r, std::forward<T>(s));
(detail::append2(r, std::forward<Args>(args)), ...);
}
template <class T>
struct SCharType;
template<> struct SCharType<std::string> { using C = char; };
template<> struct SCharType<char> { using C = char; };
template<> struct SCharType<char*> { using C = char; };
template<size_t N> struct SCharType<char[N]> { using C = char; };
template<> struct SCharType<const char*> { using C = char; };
template<> struct SCharType<std::string_view> { using C = char; };
template <class T>
using CharType = typename SCharType<std::remove_cv_t<
std::remove_reference_t<T>>>::C;
template <class T>
using StringType = std::basic_string<CharType<T>>;
namespace detail {
template <class T, class U>
constexpr void checkForSame(T&&, U&&) {
static_assert (std::is_same_v<CharType<T>, CharType<U>>,
"Should have identical char types");
}
} // ns detail
///
/// @warning
/// For semi-filled non-const buffer does strnlen.
/// For const buffer adds the entire data except the last byte
/// (very rare situation in real life)
///
template <class T, class...Args>
[[nodiscard]] inline auto concat(T&& s, Args&& ... args) {
(detail::checkForSame(std::forward<T>(s), std::forward<Args>(args)), ...);
StringType<T> r;
append(r, std::forward<T>(s), std::forward<Args>(args)...);
return r;
}
}
TEST (Concat, AsciiZerosLiteral)
{
auto r = str::concat("alpha\0\0", "bravo"sv);
EXPECT_EQ("alpha\0\0bravo"sv, r);
}
TEST (Concat, AsciiZeros1)
{
char a[10];
std::fill(std::begin(a), std::end(a), 0);
strcpy(a, "alpha");
std::string_view b = "bravo";
auto r = str::concat(const_cast<const decltype (a)&>(a), b);
EXPECT_EQ("alpha\0\0\0\0bravo"sv, r);
}