Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting Variadic template pack into std::initializer_list

Assume that there is a function which accepts several strings:

void fun (const std::initializer_list<std::string>& strings) {
  for(auto s : strings)
    // do something
}

Now, I have a variadic template function say foo() as:

template<typename ...Args>
void foo () {
  fun(???);
}

This method is called externally as:

foo<A, B, C, D>(); // where A, B, C, D are classes

And these classes which are passed as arguments are expected to contain a common static const member:

static const std::string value = "...";

Here are my questions (how to):

  1. When inside foo(), check if all the Args contain value using static_assert
  2. Pass all such values to fun() to form an initializer_list; e.g. fun({A::value, B::value, ...});

Searched several threads related to variadic templates and its unpacking but I am still novice in this area. Explanation in little more detail is much appreciated.

like image 900
iammilind Avatar asked May 10 '13 08:05

iammilind


2 Answers

The second part is easier:

template<typename ...Args>
void foo () {
   fun({Args::value...});
}

The first part is tricky, because static_assert is a declaration, not an expression, so you'd have to expand the variadic pack within the first parameter. It may be easier just to let the call to fun do the checking for you. Here's a sketch of how to do it with an auxiliary all constexpr function:

constexpr bool all() { return true; }
template<typename... Args> constexpr bool all(bool first, Args&&... rest) {
   return first && all(rest...);
}

template<typename ...Args>
void foo () {
   static_assert(all(std::is_convertible<decltype(Args::value),
      std::string>::value...), "All Args must have a value");
   fun({Args::value...});
}
like image 33
ecatmur Avatar answered Oct 08 '22 04:10

ecatmur


As for the second question, just do it this way:

template<typename ...Args>
void foo () {
  fun({Args::value...});
}

The mechanism is pretty intuitive: you create an initalizer list that contains the expanded Args::value pattern, thus resolving (in your case) to { A::value, B::value, C::value, D::value }.

Here is a complete program:

#include <string>
#include <iostream>

void fun (const std::initializer_list<std::string>& strings) {
    for(auto s : strings)
    {
        std::cout << s << " ";
    }
}

template<typename ...Args>
void foo () {
  fun({Args::value...});
}

struct A { static std::string value; };
struct B { static std::string value; };
struct C { static std::string value; };
struct D { static std::string value; };

std::string A::value = "Hello";
std::string B::value = "World";
std::string C::value = "of";
std::string D::value = "Variadic Templates";

int main()
{
    foo<A, B, C, D>(); // where A, B, C, D are classes
}

And here is a live example.

As for the static assertion, you may write a type trait that determines whether a certain type has a member variable value:

template<typename T, typename V = bool>
struct has_value : std::false_type { };

template<typename T>
struct has_value<T,
    typename std::enable_if<
        !std::is_same<decltype(std::declval<T>().value), void>::value,
        bool
        >::type
    > : std::true_type
{
    typedef decltype(std::declval<T>().value) type;
};

Then, you could use it this way:

template<typename T>
struct check_has_value
{
    static_assert(has_value<T>::value, "!");
};

template<typename ...Args>
void foo () {
    auto l = { (check_has_value<Args>(), 0)... };
    fun({Args::value...});
}

Here is a live example of a successful check (all classes has a value data member). Here is a live example of an unsuccessful check (class D's data member is called values)

like image 60
Andy Prowl Avatar answered Oct 08 '22 06:10

Andy Prowl