Преобразование пакета шаблонов Variadic в std:: initializer_list

Предположим, что существует функция, которая принимает несколько строк:

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

Теперь у меня есть вариационная функция template say foo() как:

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

Этот метод называется внешним:

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

И эти классы, которые передаются как аргументы, как ожидается, содержат общий член static const:

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

Вот мои вопросы (как):

  • Когда внутри foo(), проверьте, содержит ли все Args value, используя static_assert
  • Передайте все такие значения в fun(), чтобы сформировать initializer_list; например fun({A::value, B::value, ...});

Искал несколько тем, связанных с шаблонами variadic и его распаковкой, но я все еще новичок в этой области. Объяснение чуть более подробно оценивается.

Ответ 1

Что касается второго вопроса, просто сделайте это так:

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

Механизм довольно интуитивно понятен: вы создаете список инициализаторов, содержащий расширенный шаблон Args::value, таким образом, разрешая (в вашем случае) значение { A::value, B::value, C::value, D::value }.

Вот полная программа:

#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
}

И вот живой пример.

Что касается статического утверждения, вы можете написать характер типа, который определяет, имеет ли определенный тип переменную-член 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;
};

Затем вы можете использовать его следующим образом:

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

Ниже приведен пример живой пример успешной проверки (все классы имеют элемент данных value). Ниже представлен живой пример неудачной проверки (член класса D называется values)

Ответ 2

Вторая часть проще:

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

Первая часть сложна, потому что static_assert - это объявление, а не выражение, поэтому вам нужно будет расширить пакет вариаций в первом параметре. Может быть проще просто дать вызов fun выполнить проверку для вас. Здесь приведен пример того, как это сделать с помощью вспомогательной функции all constexpr:

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

Здесь ответ на обе точки:

#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 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
}

Посмотрите в прямом эфире.