How to concatenate static strings at compile time?
Asked Answered
S

3

32

I am trying to use templates to create an analogue of the type_info::name() function which emits the const-qualified name. E.g. typeid(bool const).name() is "bool" but I want to see "bool const". So for generic types I define:

template<class T> struct type_name { static char const *const _; };

template<class T> char const *const type_name<T>::_ = "type unknown";

char const *const type_name<bool>::_ = "bool";
char const *const type_name<int>::_ = "int";
//etc.

Then type_name<bool>::_ is "bool". For non-const types obviously I could add a separate definition for each type, so char const *const type_name<bool const>::_ = "bool const"; etc. But I thought I would try a partial specialization and a concatenation macro to derive in one line the const-qualified name for any type which has its non-const-qualified name previously defined. So

#define CAT(A, B) A B

template<class T> char const *const type_name<T const>::_
    = CAT(type_name<T>::_, " const"); // line [1]

But then type_name<bool const>::_ gives me error C2143: syntax error: missing ';' before 'string' for line [1]. I think that type_name<bool>::_ is a static string known at compile time, so how do I get it concatenated with " const" at compile time?

I tried more simple example but same problem:

char str1[4] = "int";
char *str2 = MYCAT(str1, " const");
Stockist answered 15/8, 2016 at 13:24 Comment(5)
Have a look at this: #15858641Wingfooted
related/dupe: #24783900Autocephalous
My guess would be it's totally possible with C++14 extended constexpr-s. Otherwise, you're trying to concatenate variables, not string literals...Braggadocio
You can only concatenate literals. Your last macro expands to str1 " const", which is invalid.Atencio
Seems like you're trying to do the same as libcwd::type_info_of<T>(), which can be used to print types, including top-level qualifiers. See github.com/CarloWood/libcwd/blob/master/include/libcwd/…Interference
J
39

I recently revisited this problem, and found that the previous answer I gave produced ridiculously long compile times when concatenating more than a handful of strings.

I have produced a new solution which leverages constexpr functions to remove the recursive templates responsible for the long compilation time.

#include <array>
#include <iostream>
#include <string_view>

template <std::string_view const&... Strs>
struct join
{
    // Join all strings into a single std::array of chars
    static constexpr auto impl() noexcept
    {
        constexpr std::size_t len = (Strs.size() + ... + 0);
        std::array<char, len + 1> arr{};
        auto append = [i = 0, &arr](auto const& s) mutable {
            for (auto c : s) arr[i++] = c;
        };
        (append(Strs), ...);
        arr[len] = 0;
        return arr;
    }
    // Give the joined string static storage
    static constexpr auto arr = impl();
    // View as a std::string_view
    static constexpr std::string_view value {arr.data(), arr.size() - 1};
};
// Helper to get the value out
template <std::string_view const&... Strs>
static constexpr auto join_v = join<Strs...>::value;

// Hello world example
static constexpr std::string_view hello = "hello";
static constexpr std::string_view space = " ";
static constexpr std::string_view world = "world";
static constexpr std::string_view bang = "!";
// Join them all together
static constexpr auto joined = join_v<hello, space, world, bang>;

int main()
{
    std::cout << joined << '\n';
}

This gives much quicker compile times, even with a large quantity of strings to concatenate.

I personally find this solution easier to follow as the constexpr impl function is akin to how this could be solved at runtime.

Edited with improvements thanks to @Jarod42

Justis answered 9/7, 2020 at 20:29 Comment(6)
Seems you ends value with 2 \0.Smooth
to_char_array is not really needed, if you only return a string_view. value{arr.data(), arr.size()} does the job.Smooth
You are correct on both accounts, I think at the time of writing the answer I had trouble with value{arr.data(), arr.size()} on my local compiler. I'll edit the answer with your improvementsJustis
static_assert(joined == "hello world!"); does not compile. I think the fix is ` static constexpr std::string_view value {arr.data(), arr.size() - 1};`, we don't need the null-terminator in the string_viewBerget
seems I cannot use constexpr auto joined = join_v<"hello"sv, space>;Ravel
@Fenixphoenix That's because you can't bind an rvalue to a ref template parameter, you need a constexpr objectTeenateenage
J
7

EDIT - See my new, improved answer here.


Building on @Hededes answer, if we allow recursive templates, then concatenation of many strings can be implemented as:

#include <string_view>
#include <utility>
#include <iostream>

namespace impl
{
/// Base declaration of our constexpr string_view concatenation helper
template <std::string_view const&, typename, std::string_view const&, typename>
struct concat;

/// Specialisation to yield indices for each char in both provided string_views,
/// allows us flatten them into a single char array
template <std::string_view const& S1,
          std::size_t... I1,
          std::string_view const& S2,
          std::size_t... I2>
struct concat<S1, std::index_sequence<I1...>, S2, std::index_sequence<I2...>>
{
  static constexpr const char value[]{S1[I1]..., S2[I2]..., 0};
};
} // namespace impl

/// Base definition for compile time joining of strings
template <std::string_view const&...> struct join;

/// When no strings are given, provide an empty literal
template <>
struct join<>
{
  static constexpr std::string_view value = "";
};

/// Base case for recursion where we reach a pair of strings, we concatenate
/// them to produce a new constexpr string
template <std::string_view const& S1, std::string_view const& S2>
struct join<S1, S2>
{
  static constexpr std::string_view value =
    impl::concat<S1,
                 std::make_index_sequence<S1.size()>,
                 S2,
                 std::make_index_sequence<S2.size()>>::value;
};

/// Main recursive definition for constexpr joining, pass the tail down to our
/// base case specialisation
template <std::string_view const& S, std::string_view const&... Rest>
struct join<S, Rest...>
{
  static constexpr std::string_view value =
    join<S, join<Rest...>::value>::value;
};

/// Join constexpr string_views to produce another constexpr string_view
template <std::string_view const&... Strs>
static constexpr auto join_v = join<Strs...>::value;


namespace str
{
static constexpr std::string_view a = "Hello ";
static constexpr std::string_view b = "world";
static constexpr std::string_view c = "!";
}

int main()
{
  constexpr auto joined = join_v<str::a, str::b, str::c>;
  std::cout << joined << '\n';
  return 0;
}

I used c++17 with std::string_view as the size method is handy, but this could easily be adapted to use const char[] literals as @Hedede did.

This answer is intended as a response to the title of the question, rather than the more niche problem described.

Justis answered 22/12, 2019 at 22:38 Comment(0)
F
0

(My 2 cents) In addition to @Nitronoid's answer, I use an implementation with a slightly different API but similar approach:

  • The values to concatenate are wrapped in a sequence (quite similar to std::index_sequence).
  • An additional customisable yet defaulted separator

in a fashion similar to fmt::join (from a distance).

template <
    typename T,
    std::string_view const& separator = details::default_separator
>
class join;

template <
    std::string_view const& ... values,
    std::string_view const& separator
>
class join<
    mp::sequence<std::string_view const&, values...>,
    separator
>

Which can be used like, for instance:

// assuming `name_v<T>` give a compile-time constant std::string_view value

template <std::string_view const & join_sequence, typename ... Ts>
static constexpr auto join_names = join_v<
    mp::sequence<std::string_view const &, name_v<Ts>...>,
    join_sequence
>;

auto main() -> int {
    constexpr static auto join_seq = std::string_view{" | "};
    std::cout << join_names<join_seq, int, char, int, char>;
}

Output:

int | char | int | char

See complete example here: https://godbolt.org/z/d89z7n8PT.

Franks answered 16/5 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.