Explicit template instantiation for variadic templates

1.1k views Asked by At

Let's say we have a wrapping template function (or member function) like this:

template <class... T> void f(T... args) {
    _f(args...);
}
void _f(int a1, ...); // variadic old style function

Where the function _f (defined in a third-party library that we cannot modify) accepts several combinations of parameter types and it would be desirable to limit possible parameters at wrap level.
If we have hundreds of such functions with different possible parameter types it would be too bulky to manually define the overloaded functions with named arguments. It would be better to define such functions using simple macro with lists of possible types. Boost's macros that allows to iterate argument's lists to construct named arguments looks too heavy.
Is there an elegant way to limit possible arguments at declaration level?

2

There are 2 answers

0
skypjack On BEST ANSWER

You can use traits to define accepted lists of parameters based on their types.
It follows a minimal, working example:

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

The last invokation will give you a compile time error for <int, int, int> isn't a valid specialization of struct accepts.
This is because the primary template isn't defined and you can control the accepted lists by introducing more and more specializations if needed.


Here is a slightly different solution based on std::true_type, std::false_type and 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);
}

Advantages are:

  • a more meaningful error message at compile-time (well, if you replace "!" with something meaningful)
  • the possibility to explicitly disable a list of parameters by inheriting from std::false_type and document it at the same time if needed

The disadvantage is that it's a bit more verbose.

1
Quentin On

Well, hvd is spot-on. What you want really is SFINAE. But I had already gone off to implement it with Boost.PP, sooo...

#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)); \
    }

Expands to:

void f( int _0 , double _1 , std::string _2 ) { _f( _0 , _1 , _2 ); }