Есть ли хороший способ применить ограничения типа для параметров функции в вариативном шаблоне на С++?

У меня есть перечисление, назовем его Type. Он имеет значения как таковые:

enum Type { STRING, TYPE_A_INT, TYPE_B_INT};

Я хочу написать функцию Foo, которая может принимать произвольное количество значений типа {int, string}, но обеспечивать, чтобы параметры шаблона соответствовали типам параметров.

В идеале он будет вести себя так:

Foo<STRING, TYPE_A_INT>("str", 32); // works
Foo<STRING, TYPE_B_INT>("str", 32);  // works
Foo<STRING, TYPE_B_INT, TYPE_A_INT, STRING>("str", 32, 28, "str");  // works
Foo<STRING, TYPE_B_INT>("str", "str");  // doesn't compile

Есть ли способ сделать это?

Кажется, я мог бы сделать что-то вроде следующего, но это не сработает, поскольку Args будет Type, а args будет {string, int}.

template<typename Arg, typename... Args>
std::enable_if<(std::is_same<Arg, STRING>::value)> 
Foo(String arg, Args... args) {
    // Do stuff to arg, then make recursive call.
    Foo(args);
}

template<typename Arg, typename... Args>
std::enable_if<(std::is_same<Arg, TYPE_A_INT>::value)> 
Foo(int arg, Args... args) {
    // Do stuff to arg, then make recursive call.
    Foo(args);
}

Я мог бы просто обернуть аргументы чем-то вроде

pair<Type, string>
pair<Type, int>

но было бы очень хорошо избежать этого.


person rhi    schedule 10.11.2015    source источник
comment
В разделе Do stuff to arg не будет ли ошибки компилятора, если тип все равно не будет работать? Зачем вам ограничивать его, если компилятор должен сделать это за вас?   -  person Kevin    schedule 11.11.2015
comment
Почему бы не реализовать static_assert в функции шаблона, которая использует std::is_same для запроса одного из разрешенных типов?   -  person Olipro    schedule 11.11.2015


Ответы (1)


Одним из простых способов сделать это было бы создать сопоставление перечислителей с желаемыми типами и использовать его для построения списка параметров функции - я думаю, вы могли бы думать об этом как о "чертах перечислителя":

#include <iostream>
#include <string>

enum Type {STRING, TYPE_A_INT, TYPE_B_INT};

template<Type> struct type_from;

template<> struct type_from<STRING> { using type = std::string; };
template<> struct type_from<TYPE_A_INT> { using type = int; };
template<> struct type_from<TYPE_B_INT> { using type = int; };

template<Type E> using type_from_t = typename type_from<E>::type;

template<Type... Es> void Foo(type_from_t<Es>... args)
{
   // Do stuff with args.
   using expander = int[];
   (void)expander{0, (std::cout << args << ' ', 0)...};
   std::cout << '\n';
}

int main()
{
   Foo<STRING, TYPE_A_INT>("str", 32); // works
   Foo<STRING, TYPE_B_INT>("str", 32);  // works
   Foo<STRING, TYPE_B_INT, TYPE_A_INT, STRING>("str", 32, 28, "str");  // works
   // Foo<STRING, TYPE_B_INT>("str", "str");  // doesn't work
}

Если вы раскомментируете последнюю строку, вы получите красивое сообщение об ошибке, в котором будет указано, какой именно аргумент вызывает проблему.

Конечно, это не гарантирует, что типы аргументов функции в точности совпадают с теми, которые заданы типажами перечислителя, а гарантирует, что для каждого из них существует допустимое неявное преобразование. Насколько я понимаю, это то, что вы хотите, так как ваш пример передает строковые литералы в std::strings.

person bogdan    schedule 11.11.2015