While upgrading some meta-program running in production for years to the latest (c++20) standard, I rewrote this particular compile time string utility.
Even though this new version produces desired output and correctly evaluates at compile time as well as dynamically, it's difficult to get a grasp on a new standard as always.
This example is sufficient for my particular use case. And for now I can only spot three inconsistencies I'd like to improve: two of them are marked in the code snippet with TODO, and the third one is tiding it up with concepts (like convertable_to, which is deliberately omitted in the code for simplicity sake)
I'm looking for a general case advice on how this code can be improved further. Any other weighted criticism will be much appreciated.
UPDATE: New question with all of the suggestions integrated
Original code:
#ifndef META_STRING_H_INCLUDED
#define META_STRING_H_INCLUDED
#include <cstddef>
#include <algorithm>
#include <functional>
#include <tuple>
namespace meta
{
template <std::size_t N>
struct string;
constexpr auto to_string(const auto& input) { return string(input); }
constexpr auto concat(const auto&... input) { return string(to_string(input)...); }
template <std::size_t N>
struct string
{
char elems[N];
// string() = delete;
string() { elems[N - 1] = '\0'; } // used for CTAD guide for tuples. can't we avoid object construction there?
constexpr string(const char (&s)[N])
{
std::copy_n(s, N, this->elems);
}
constexpr string(const string<N> (&s))
{
std::copy_n(s.elems, N, this->elems);
}
constexpr string(const std::array<char, N> (&s))
{
std::copy_n(s.data(), N, this->elems);
}
template <std::size_t... Ni>
constexpr void _copy(const string<Ni> (&... input))
{
auto pos = elems;
((pos = std::copy_n(input.elems, Ni - 1, pos)), ...);
*pos = 0;
}
constexpr string(const auto&... input) requires (sizeof...(input) > 1)
{
std::invoke([this](const auto&... s) constexpr { this->_copy(s...); }, to_string(input)...);
}
template <template <typename...> typename container, typename... T>
constexpr string(const container<T...>& input)
{
std::apply([this](const auto&... s) constexpr { this->_copy(to_string(s)...); }, input);
}
constexpr auto operator + (const auto& rhs) const
{
return concat(*this, rhs);
}
constexpr operator const char* () const { return elems; }
};
template<std::size_t N> string(const char (&)[N])
-> string<N>;
template<std::size_t N> string(const std::array<char, N>& input)
-> string<N>;
string(const auto&... input)
-> string<((sizeof(to_string(input).elems) - 1) + ... + 1)>;
template<template <typename...> typename container, typename... T> string(const container<T...>& input)
-> string<((sizeof(to_string(T()).elems) - 1) + ... + 1)>; // TODO: avoid constructing object here
inline namespace meta_string_literals {
template<string ms>
inline constexpr auto operator"" _ms() noexcept
{
return ms;
}
} // inline namespace meta_string_literals
} // namespace meta
#endif // META_STRING_H_INCLUDED
//////////////////////////////////////////////////////////////////////
// #include "meta_string.h"
#include <iostream>
template<meta::string str>
struct X
{
static constexpr auto value = str;
operator const char* () { return str.elems; }
};
template <auto value>
constexpr inline auto constant = value;
int main()
{
using namespace meta::meta_string_literals;
X<"a message"> xxx;
X<"a massage"> yyy;
X<meta::concat(xxx.value, " is not ", yyy.value)> zzz;
X<"a message"_ms + " is " + "a massage"> zzz2;
std::cout << xxx << std::endl;
std::cout << yyy << std::endl;
std::cout << zzz << std::endl;
std::cout << zzz2 << std::endl;
static constexpr auto x = meta::string("1"_ms, "22");
static constexpr auto y = meta::concat("11", "22");
static constexpr auto z = meta::string(std::tuple{"1xx1"_ms, std::array<char, 6>{"2qqq2"}});
std::cout << sizeof(x.elems) << ": " << x << std::endl;
std::cout << sizeof(y.elems) << ": " << y << std::endl;
std::cout << sizeof(z.elems) << ": " << z << std::endl;
static constexpr auto a = "1"_ms;
static constexpr auto b = a + "22"_ms;
std::cout << b << std::endl;
// TODO: Can't it be implicitly forced to constexpr?
std::cout << meta::string("this one "_ms, "is not ", "constant evaluated"_ms) << std::endl;
std::cout << constant<meta::string("this one "_ms, "is ", "constant evaluated"_ms)> << std::endl;
return 0;
}
std::string_view, as well aschar*. It’s becoming common to acceptstd::string_viewas the common denominator of all string formats. \$\endgroup\$string_view, in implementing things like a variadic concatenate-everything function, that avoids heap allocation. \$\endgroup\$