1

I managed to solve a previous question about initializing a static char array, asked here: Initializing a static char based on template parameter

I don't like the need for a secondary function in my solution:

//static char arr[N] = {[0]='0', [1]='x', [N-1]='\0',};
// ideally want this, but not currently implemented in g++

template <char... chars>
struct zero_str {};

template <unsigned N, char... chars>
struct seq_gen { using type = typename seq_gen<N-1, '0', chars...>::type; };

template <char... chars>
struct seq_gen<0, chars...> { using type = zero_str<chars...>; };

template <size_t N>
struct zero_gen { using type = typename seq_gen<N-1, '0', '\0'>::type; };

template <>
struct zero_gen<0> { using type = zero_str<'\0'>; };

template<size_t N> using strsize = typename zero_gen<N>::type;

template<typename T, char... chars>
const char* n2hexHelper(T val, zero_str<chars...>)
{
    thread_local static char hexstr[] = {'0', 'x', chars...};
    /* convert to hex */
    return hexstr;
};

template<typename T>
const char* n2hex(T val)
{
    return n2hexHelper<T> (val, strsize<sizeof(T)*2>() );
}

int main()
{
    std::cout << n2hex(1) << std::endl;
    return EXIT_SUCCESS;
}

Instead, I'd prefer not to need the dummy variable passed to the helper function, and be able to do something like this:

template<typename T, char... chars>
const char* n2HexIdeal(T val)
{
    thread_local static char hexstr[] = {'0', 'x', chars...}; //how to get chars... ?
    /* convert to hex */
    return hexstr;
}

I have two main questions. 1) is something like my ideal case possible with parameter pack expansions? Or is the only way to force the compiler to deduce my char... is to use it as a function parameter? 2) I'm not very familiar with template metaprogramming, so I was wondering if there are any glaring faults or idiomatic missteps with my above solution.

Community
  • 1
  • 1
Mike Lui
  • 1,301
  • 10
  • 23

2 Answers2

1

If you can accept that your helper function is a static method of a class/struct, you can use the partial specialization as follows

template <typename, typename>
struct n2hexH;

template <typename T, char ... Chs>
struct n2hexH<T, zero_str<Chs...>>
 {
   static char * func (T const & val)
    {
      thread_local static char hexstr[] = {'0', 'x', Chs...};
      /* convert to hex */
      return hexstr;
    }
 };

Now, your n2hex() function become

template<typename T>
const char* n2hex(T val)
 { return n2hexH<T, strsize<(sizeof(T)<<1U)>>::func(val); }

or you can tranform it in a macro

#define n2hex(X) n2hexH<decltype(X), strsize<(sizeof(X)<<1U)>>::func(X)
max66
  • 65,235
  • 10
  • 71
  • 111
  • Hmm I still don't like the additional function call but I guess any decent optimization would just forward straight to the function. Is something like the following possible?'template /* somehow use U to get char... chars? */>' – Mike Lui Feb 27 '17 at 15:54
  • @MitchLaskis - it's possible to write `template` but, this way, you don't get the `char ... chars` (you don't have the pack `chars` to expand). The only way I know is pass a variable of type `strsize<>` (or the type, as template parameter) to another function/method. – max66 Feb 27 '17 at 16:18
1

This is doable in C++14.

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f)->decltype(auto) {
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto index_over( std::integral_constant< std::size_t, N > ={} ) {
  return index_over( std::make_index_sequence<N>{} );
}

These are two helper functions that allow you to expand a bunch of std::size_t compile-time values without a "custom" helper function at each point of use.

We can then use them:

template<typename T>
const char * toStr(T num)
{
  thread_local static
  auto str = index_over<sizeof(T)*3>()
  ([&](auto...Is)->std::array<char, 3+3*sizeof(T)> {
    return {{ '0', 'x',
      (void(Is),'0')...,
      '\0'
    }};
  });
  // do something with str
  (void)num;
  return str.data();
}

to generate and expand our parameter packs inline.

This requires auto variardic lambdas, which is why it doesn't work in C++11.

live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    I was looking for a C++11 answer originally, but I like how this answer makes me scratch my head. Additionally I realized a simple templated struct, whose constructor just sets the values is a much more idiomatic solution, but it was a good introduction to toying around with variadic templates. – Mike Lui Mar 01 '17 at 08:21