Mike Lui
Mike Lui

Reputation: 1371

Parameter pack expansion questions

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.

Upvotes: 1

Views: 129

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275510

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.

Upvotes: 1

max66
max66

Reputation: 66210

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)

Upvotes: 1

Related Questions