0

I have a class where I use the length of the string literal as a template parameter. This way, the length of the literal is evaluated at compile time, and there's no reason to do strlen (or equivalent) at runtime. I would like to do the same with an array of string literals.

// Handler class
class Handler {
    template<size_t N>
    bool String(const char(&str)[N]) { /* ... */ }
};

Handler myHandler;

// Works
myHandler.String("it");

// Handler::String(const char (&)[N])': could not deduce template argument for 'const char (&)[N]' from 'const char *const *'
const char* manyIts[3] = {"this", "and", "that" };
for (int i = 0; i < 3; ++i) {
    myHandler.String(manyIts[i]);
}

I understand that fact the strings are literals is lost when storing them in a const char* array. Is there some other convenient way to store these to allow this sort of functionality?

MuertoExcobito
  • 9,741
  • 2
  • 37
  • 78

1 Answers1

1

The problem is that arrays must have elements of uniform type. Since you are encoding the size of your strings in the type, you cannot have an array of strings of different length. You can use std::tuple to produce a compile-time array of elements of different types. The trick is preventing the character arrays from decaying to pointers to the first element.

Using variadic templates, you can write a function that return a tuple of pointers to const char arrays.

#include <tuple>

// Define an alias for pointer to const char array
template<size_t N>
struct t_str_ptr { using type = const char(*)[N]; };

// Returns a tuple of pointers to cont char arrays
template<size_t ... N>
auto make_str_tuple(const char(&...p_str)[N])
{
    return std::make_tuple<typename t_str_ptr<N>::type...>(&p_str...);
}

Simply using std::make_tuple and relying on argument deduction will produce a std::tuple of const char * which will lose the size information.

The usage becomes :

int main()
{
    Handler myHandler;

    // Works
    myHandler.String("it");

    // Works
    auto manyIts = make_str_tuple( "this", "and", "that" );

    myHandler.String(*std::get<0>(manyIts));
    myHandler.String(*std::get<1>(manyIts));
    myHandler.String(*std::get<2>(manyIts));
}

The challenge now is iterating over the tuple. Since each element has it's own type, it's not quite as easy as writing a for loop. Notice that the index in std::get is a template argument. To avoid duplication, see this question on iterating over std::tuple.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87