-1

I'm trying to generate a simple format string for fmt at compile time, but I can't quite figure out the string concatenation. I'm limited to c++ 14.

What I'd like is to be able to generate a format string for N items, so it could be used as follows: auto my_string = fmt::format(format_string_struct<2>::format_string, "Item", "key1", "value1", "key2", "value2");

The generated format string would be: "{} {}={} {}={}", resulting in a formatted string of "Item key1=value1 key2=value2". The base of the format string would be "{}", and it appends a " {}={}" for each N.

I'm trying to use recursive templates, but I just can't quite get everything to work. I got some code from Concatenate compile-time strings in a template at compile time? for concatenating strings (though I get a undefined reference to `concat_impl<&base_format_string_struct::format_string, std::integer_sequence<int, 0, 1>, &format_string_parameter_struct::parameter_string, std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7> >::value' error)

template<int...I> using is      = std::integer_sequence<int,I...>;
template<int N>   using make_is = std::make_integer_sequence<int,N>;

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

template<const char*, typename, const char*, typename>
struct concat_impl;

template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
    static constexpr const char value[]
    {
        S1[I1]..., S2[I2]..., 0
    };
};

template<const char* S1, const char* S2>
constexpr auto concat {
    concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value
};

struct base_format_string_struct {
    static constexpr const char format_string[] = "{}";    
};

struct format_string_parameter_struct {
    static constexpr const char parameter_string[] = " {}=\"{}\"";
};

template <int arg_count, const char[]... params>
struct format_string_struct:
    public format_string_struct<arg_count - 1, format_string_parameter_struct, params...> {
};

template <int arg_count>
struct format_string_struct:
    public format_string_struct<arg_count - 1, format_string_parameter_struct> {
};

template <const char[]... params>
struct format_string_struct<0> {
  static const char* format_string() {
    return concat<base_format_string_struct, params...>;
  }
};


康桓瑋
  • 33,481
  • 5
  • 40
  • 90
Rob W.
  • 362
  • 5
  • 18
  • That's not the error I get. Please provide a [mre] – Ted Lyngmo Apr 06 '22 at 16:59
  • Yes, the error I listed was in regards to a simplified attempt to just use the concat<> template that I had gotten from the other SO page. I'm not tied to that string concatenation solution. My question here isn't about finding a fix for a single error, rather I'm looking for a usable solution for generating the entire string I need. – Rob W. Apr 06 '22 at 18:03
  • The problem is that the code you show and the error you say that you get does not match so we don't know what we start out with. Either change the code so that we get the same error message, or show the actual error message for the code you've shown. Mixing them is rarely a good idea. – Ted Lyngmo Apr 06 '22 at 18:07
  • The code I presented was to show the general idea of what I'm trying to accomplish. The error I highlighted was to illustrate a difficulty I ran into with the concat<> code. Feel free to ignore that aside if it's confusing you. Jarod42 provided a solution based on my code below, but unfortunately that requires a newer compiler than I can use in this situation. – Rob W. Apr 06 '22 at 18:50

1 Answers1

2

You have several typos (and a concat limited to 2 argument).

template<int...I> using is      = std::integer_sequence<int,I...>;
template<int N>   using make_is = std::make_integer_sequence<int,N>;

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

template<const char*, typename, const char*, typename>
struct concat_impl;

template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
    static constexpr const char value[]
    {
        S1[I1]..., S2[I2]..., 0
    };
};

template<const char* S1, const char* S2, const char*... Ss>
struct concat
{
    constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, concat<S2, Ss...>::value, make_is<size(concat<S2, Ss...>::value)>>::value;
};

template<const char* S1, const char* S2>
struct concat<S1, S2>
{
    constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value;
};

struct base_format_string_struct {
    static constexpr const char format_string[] = "{}";    
};

struct format_string_parameter_struct {
    static constexpr const char parameter_string[] = " {}=\"{}\"";
};

template <int arg_count, const char*... params>
struct format_string_struct:
    public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string, params...> {
};

template <int arg_count>
struct format_string_struct<arg_count>:
    public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string> {
};

template <const char*... params>
struct format_string_struct<0, params...> {
  static const char* format_string() {
    return concat<base_format_string_struct::format_string, params...>::value;
  }
};

Demo

But I would probably go with constexpr functions:

#if 1 // missing some constexpr for std::array/std::copy

template <typename T, std::size_t N>
struct my_array
{
    T data[N];
};

template <typename CIT, typename IT>
constexpr void copy(CIT begin, CIT end, IT dest)
{
    for (CIT it = begin; it != end; ++it)
    {
        *dest = *it;
        ++dest;
    }
}
#endif

template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct_impl()
{
    my_array<char, 2 + 8 * N + 1> res{};
    constexpr char init[] = "{}"; // size == 2
    constexpr char extra[] = R"( {}="{}")"; // size == 8
    copy(init, init + 2, res.data);
    for (std::size_t i = 0; i != N; ++i) {
        copy(extra, extra + 8, res.data + 2 + i * 8);
    }
    return res;
}

template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct()
{
    // Ensure the computation is done compile time.
    constexpr auto res = format_string_struct_impl<N>();
    return res;
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Unfortunately, this doesn't work for me, as it seems to require C++ > 14. – Rob W. Apr 06 '22 at 18:00
  • Specifically: :23:145: error: 'concat<(& format_string_parameter_struct::parameter_string), (& format_string_parameter_struct::parameter_string)>::value' is not a valid template argument because 'concat<(& format_string_parameter_struct::parameter_string), (& format_string_parameter_struct::parameter_string)>::value' is a variable, not the address of a variable – Rob W. Apr 06 '22 at 18:06
  • @RobW.: Add alternative with `constexpr` functions (done in C++14). – Jarod42 Apr 07 '22 at 11:17
  • Perfect! I like the constexpr function much better than the template mess I was initially chasing. – Rob W. Apr 07 '22 at 12:59