Converting Variadic template pack into std::initia

2019-03-13 17:03发布

问题:

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.

回答1:

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)



回答2:

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...});
}


回答3:

Here's an answer to both points:

#include <initializer_list>
#include <iostream>
#include <string>
#include <type_traits>

using namespace std;

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

// This uses SFINAE to find if there's a string T::value in T
template <typename T>
struct HasValue
{
    typedef char OK; //sizeof() guaranteed 1
    struct BAD { char x[2]; }; //sizeof() guaranteed >1

    template <const string *>
    struct Helper;

    template <typename X>
    static OK has(X*, Helper<&X::value>* = nullptr); //SF if &X::value is not a const string*

    static BAD has(...);  //will be picked in SF case

    static const bool value = (sizeof(has((T*)nullptr)) == sizeof(OK));
};


// This template (and its specialisation) ensure all args have ::value
template <typename H, typename... T>
struct HaveValue : public integral_constant<bool, HasValue<H>::value && HaveValue<T...>::value>
{};

template <typename H>
struct HaveValue<H> : public HasValue<H>
{};



template <typename... Args>
void foo() {
    static_assert(HaveValue<Args...>::value, "All arguments must have const string ::value");
    fun({Args::value...});  //answer to point 2: create the initialiser list
}

// Example data follow
struct A
{
    static const string value;
};
const string A::value = "AA";

struct B
{
    static const string value;
};
const string B::value = "BB";

struct C{};

int main()
{
    foo<A, B>();
    //foo<A, B, C>();  //uncomment to have the static assertion fire
}

See it live.