Variadic template combined with default template argument
Asked Answered
S

2

8

Suppose I have a class

enum CallbackType
{
    SYNC,
    ASYNC
}


template<CallbackType CB = SYNC, typename... Args>
class Callback
{
}

I would like to be able to optionally specificy the callback type while still being ablet to have variadic template argument. Now I understand that the compiler can't tell them apart, but maybe there is some way to handle the specific case where the first template argument is of CallbackType ?

Callback<int int> //Should be Callback<SYNC, int, int>
Callback<ASYNC, int, int> //Should be Callback<ASYNC, int, int>
Self answered 4/8, 2017 at 11:1 Comment(0)
J
10

There are two aspects of C++, when it comes to variadic templates, that are in conflict with each other in your case:

  1. Defaulted template parameters should not precede non-defaulted template parameters.

  2. Variadic template parameters should not precede non-variadic template parameters.

It is certainly possible, in many situations, to correctly declare, and use, templates whose parameters don't follow these rules, but those situations are not important for the purpose of this question. In your case, what it comes down to is that both of your template parameters want to be the last parameter in their template, for their own individual reasons. That's the problem, in a nutshell.

The easiest way to resolve this conflict is to use an inner template:

template<CallbackType CB = ASYNC>
class CallbackClass {

public:

    template<typename... Args> class Callback
    {
    }
};

Then, your two examples become:

CallbackClass<>::Callback<int, int>

and

CallbackClass<ASYNC>::Callback<int, int>

You'll end up with longer class names, of course. But that's what typedef and using is for. For example:

template<typename ...Args>
using DefaultCallback=CallbackClass<>::Callback<Args...>;

then use

DefaultCallback<int, int>
Justiciar answered 4/8, 2017 at 11:13 Comment(1)
Too bad we can't have nice things :(, I would like it to always take CallbackType as a the enum template argument and the rest as variadic, but I geuss it doesn't tell things apart like that.Self
O
3

You can get a syntax very close to your original syntax with a bit of metaprogramming. You'd define your CallbackType and a CallbackImpl:

enum CallbackType
{
    SYNC,
    ASYNC,
};

template<CallbackType CB, typename... Args>
class CallbackImpl
{
};

Then do some things to get "default arguments":

// We need to treat the CallbackType argument as a type, not as a value.
// Thus, we need to wrap it in a type.
template <CallbackType cb>
using CallbackT = std::integral_constant<CallbackType, cb>;

// We need to be able to detect if the first type passed in is this CallbackT
template <typename T>
struct is_callback_type
    : std::false_type
{};

template <CallbackType cb>
struct is_callback_type<CallbackT<cb>>
    : std::true_type
{};

template <typename T>
using is_callback_type_t = typename is_callback_type<T>::type;

// Here we do the work. This is the base case, where the first arg
// is not a CallbackT. Note that this works for an empty Args as well
template <typename AlwaysVoid, typename... Args>
struct construct_callback_impl
{
    using type = CallbackImpl<SYNC, Args...>;
};

// If the Args list is of at least size 1,
template <typename CallbackType, typename... Args>
struct construct_callback_impl<
    // Use this specialization only if the first type is our CallbackT
    typename std::enable_if<is_callback_type_t<CallbackType>::value>::type,
    CallbackType,
    Args...>
{
    // Forward the specified CallbackType on to the CallbackImpl
    using type = CallbackImpl<CallbackType::value, Args...>;
};

// Wrap this utility into a nicer calling syntax    
template <typename... Args>
using Callback = typename construct_callback_impl<void, Args...>::type;

Then, it can be used:

Callback<int, int>                   // type is CallbackImpl<SYNC, int, int>
Callback<CallbackT<SYNC>, int, int>  // type is CallbackImpl<SYNC, int, int>
Callback<CallbackT<ASYNC>, int, int> // type is CallbackImpl<ASYNC, int, int>
Callback<>                           // type is CallbackImpl<SYNC>

Live on Godbolt

I think it's pretty clear why this isn't usually done.

Ofay answered 4/8, 2017 at 20:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.