【问题标题】:Variadic template combined with default template argument可变参数模板与默认模板参数相结合
【发布时间】:2017-08-04 11:01:00
【问题描述】:

假设我有一堂课

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>

【问题讨论】:

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


    【解决方案1】:

    当涉及到可变参数模板时,C++ 有两个方面在您的情况下相互冲突:

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

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

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

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

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

    那么,你的两个例子就变成了:

    CallbackClass<>::Callback<int, int>
    

    和

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

    当然,你最终会得到更长的类名。但这就是typedef 和using 的用途。例如:

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

    然后使用

    DefaultCallback<int, int>
    

    【讨论】:

    • 太糟糕了,我们不能有好的东西:(,我希望它总是将 CallbackType 作为枚举模板参数,其余的作为可变参数,但我猜它并不能区分事情像这样。
    【解决方案2】:

    您可以通过一些元编程获得非常接近原始语法的语法。您将定义您的 CallbackType 和 CallbackImpl:

    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

    我认为这很清楚为什么通常不这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-06
      • 2012-08-07
      • 1970-01-01
      • 2015-08-15
      • 2016-12-01
      • 1970-01-01
      • 2013-02-28
      • 1970-01-01
      相关资源
      最近更新 更多