Явная реализация шаблона для вариационных шаблонов
Допустим, у нас есть функция шаблона упаковки (или функция-член), подобная этой:
template <class... T> void f(T... args) {
_f(args...);
}
void _f(int a1, ...); // variadic old style function
Где функция _f
(определено в сторонней библиотеке, которую мы не можем изменить) принимает несколько комбинаций типов параметров, и было бы желательно ограничить возможные параметры на уровне переноса.
Если бы у нас было сотни таких функций с различными возможными типами параметров, было бы слишком громоздко определять перегруженные функции с именованными аргументами вручную. Было бы лучше определить такие функции, используя простой макрос со списками возможных типов. Макросы Boost, позволяющие перебирать списки аргументов для создания именованных аргументов, выглядят слишком тяжелыми.
Есть ли элегантный способ ограничить возможные аргументы на уровне декларации?
2 ответа
Вы можете использовать черты для определения принятых списков параметров, основанных на их типах.
Следует минимальный рабочий пример:
void _f(int, ...) {}
template<typename...> struct accepts;
template<> struct accepts<int, double, char> {};
template<> struct accepts<int, char, int> {};
template <class... T>
auto f(T... args) -> decltype(accepts<T...>{}, void()) {
_f(args...);
}
int main() {
f(0, 0., 'c');
f(0, 'c', 0);
// f(0, 0, 0);
}
Последнее обращение даст вам ошибку времени компиляции для <int, int, int>
не является действительной специализацией структуры accepts
,
Это потому, что основной шаблон не определен, и вы можете контролировать принятые списки, вводя все больше и больше специализаций, если это необходимо.
Вот немного другое решение, основанное на std::true_type
, std::false_type
а также static_assert
:
#include<type_traits>
void _f(int, ...) {}
template<typename...> struct accepts: std::false_type {};
template<> struct accepts<int, double, char>: std::true_type {};
template<> struct accepts<int, char, int>: std::true_type {};
template <class... T>
void f(T... args) {
static_assert(accepts<T...>::value, "!");
_f(args...);
}
int main() {
f(0, 0., 'c');
f(0, 'c', 0);
// f(0, 0, 0);
}
Преимущества:
- более значимое сообщение об ошибке во время компиляции (ну, если вы замените
"!"
с чем-то осмысленным) - возможность явного отключения списка параметров путем наследования от
std::false_type
и документируйте это одновременно при необходимости
Недостатком является то, что это немного более многословно.
Ну, HVD на месте. Что вы действительно хотите, так это СФИНА. Но я уже ушел, чтобы реализовать это с Boost.PP, ооочень...
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/comma_if.hpp>
#define DECL_PARAM(r, data, i, elem) \
BOOST_PP_COMMA_IF(i) elem _ ## i
#define FWD_PARAM(r, data, i, elem) \
BOOST_PP_COMMA_IF(i) _ ## i
#define DEF_FN_WRAPPER(name, delegate, params) \
void name(BOOST_PP_SEQ_FOR_EACH_I(DECL_PARAM, ~, params)) { \
delegate(BOOST_PP_SEQ_FOR_EACH_I(FWD_PARAM, ~, params)); \
}
Расширяется до:
void f( int _0 , double _1 , std::string _2 ) { _f( _0 , _1 , _2 ); }