可变参数模板与默认模板参数结合使用

时间:2017-08-04 11:01:00

标签: c++ c++11 template-meta-programming

假设我有一个班级

enum CallbackType
{
    SYNC,
    ASYNC
}


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

我希望能够选择特定的回调类型,同时仍然能够拥有可变参数模板参数。现在我明白编译器不能区分它们,但也许有办法处理第一个模板参数是CallbackType的特定情况?

Callback<int int> //Should be Callback<SYNC, int, int>
Callback<ASYNC, int, int> //Should be Callback<ASYNC, int, int>

2 个答案:

答案 0 :(得分:8)

当涉及可变参数模板时,C ++有两个方面,在你的情况下彼此冲突:

  1. 默认模板参数不应位于非默认模板参数之前。

  2. 变量模板参数不应位于非变量模板参数之前。

  3. 在许多情况下,当然可以正确声明和使用参数不遵循这些规则的模板,但这些情况对于此问题的目的并不重要。在您的情况下,它归结为两个模板参数都希望成为其模板中的最后一个参数,这是出于各自的原因。简而言之,这就是问题所在。

    解决此冲突的最简单方法是使用内部模板:

    template<CallbackType CB = ASYNC>
    class CallbackClass {
    
    public:
    
        template<typename... Args> class Callback
        {
        }
    };
    

    然后,你的两个例子变成了:

    CallbackClass<>::Callback<int, int>
    

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

    当然,你最终会有更长的班级名称。但那是typedefusing的用途。例如:

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

    然后使用

    DefaultCallback<int, int>
    

答案 1 :(得分:2)

通过一些元编程,您可以获得与原始语法非常接近的语法。您要定义CallbackTypeCallbackImpl

enum CallbackType
{
    SYNC,
    ASYNC,
};

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

然后做一些事情来获得“默认参数”:

// 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;

然后,它可以使用:

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

我认为很明显为什么通常不这样做。