Search code examples
c++c++17variadic-templatestemplate-meta-programmingtemplate-specialization

How to compose a string literal from constexpr char arrays at compile time?


I'm trying to create a constexpr function that concatenates const char arrays to one array. My goal is to do this recursively by refering to a specialized variable template join that always joins two const char*'s . But the compiler doesn't like it and throws an error message that I can't get behind.

I've already checked out this topic but it unfortunately doesn't have a straight up answer.

Code:

#include <type_traits>
#include <cstdio>
#include <iostream>

constexpr auto size(const char*s)
{
    int i = 0;
    while(*s!=0) {
        ++i;
        ++s;
    }
    return i;
}

template <const char* S1, typename, const char* S2, typename>
struct join_impl;

template <const char* S1, int... I1, const char* S2, int... I2>
struct join_impl<S1, std::index_sequence<I1...>, S2, std::index_sequence<I2...>>
{
    static constexpr const char value[]{ S1[I1]..., S2[I2]..., 0 };
};

template <const char* S1, const char* S2>
constexpr auto join
{
    join_impl<S1, std::make_index_sequence<size(S1)>, S2, std::make_index_sequence<size(S2)>>::value
};

template <const char* S1, const char* S2, const char*... S>
struct join_multiple
{
    static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
};

template <const char* S1, const char* S2>
struct join_multiple<S1, S2>
{
    static constexpr const char* value = join<S1, S2>;
};


constexpr const char a[] = "hello";
constexpr const char b[] = "world";
constexpr const char c[] = "how is it going?";

int main()
{
    // constexpr size_t size = 100;
    // char buf[size];
    // lw_ostream{buf, size};

    std::cout << join_multiple<a, b, c>::value << std::endl;
}

Error:

<source>:33:82: error: qualified name refers into a specialization of variable template 'join'
    static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
<source>:25:16: note: variable template 'join' declared here
constexpr auto join
               ^
<source>:33:34: error: default initialization of an object of const type 'const char *const'
    static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
                                 ^
                                       = nullptr
2 errors generated.
ASM generation compiler returned: 1
<source>:33:82: error: qualified name refers into a specialization of variable template 'join'
    static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
<source>:25:16: note: variable template 'join' declared here
constexpr auto join
               ^
<source>:33:34: error: default initialization of an object of const type 'const char *const'
    static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
                                 ^
                                       = nullptr
2 errors generated.
Execution build compiler returned:

What am I missing?


Solution

  • As alternative, to avoid to build the temporary char arrays, you might work with types (char sequences) and create the char array variable only at the end, something like:

    constexpr auto size(const char*s)
    {
        int i = 0;
        while(*s!=0) {
            ++i;
            ++s;
        }
        return i;
    }
    
    template <const char* S, typename Seq = std::make_index_sequence<size(S)>>
    struct as_sequence;
    
    template <const char* S, std::size_t... Is>
    struct as_sequence<S, std::index_sequence<Is...>>
    {
        using type = std::integer_sequence<char, S[Is]...>;
    };
    
    template <typename Seq>
    struct as_string;
    
    template <char... Cs1>
    struct as_string<std::integer_sequence<char, Cs1...>>
    {
        static constexpr const char c_str[] = {Cs1..., '\0'};
    };
    
    template <typename Seq1, typename Seq2, typename... Seqs>
    struct join_seqs
    {
        using type = typename join_seqs<typename join_seqs<Seq1, Seq2>::type, Seqs...>::type;
    };
    
    template <char... Cs1, char... Cs2>
    struct join_seqs<std::integer_sequence<char, Cs1...>, std::integer_sequence<char, Cs2...>>
    {
        using type = std::integer_sequence<char, Cs1..., Cs2...>;
    };
    
    template <const char*... Ptrs>
    const auto join =
        as_string<typename join_seqs<typename as_sequence<Ptrs>::type...>::type>::c_str;
    

    Demo