【问题标题】:Enable template only for specific templated class仅为特定模板类启用模板
【发布时间】:2016-06-17 06:09:04
【问题描述】:

这个问题的灵感来自我之前的问题No template parameter deduction of parameter pack

考虑以下代码示例:

#include <memory>
#include <string>

template<typename... FArgs>
class Callback
{
    public:
    class Handle{};
};

class BaseCallbackHandle
{
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template<typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
   return {};
}

int main()
{
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error
}

另见http://coliru.stacked-crooked.com/a/5f2a2e816eef6afd

函数模板makeTypeErasedCallbackHandle 现在将任何类作为输入参数。有什么方法可以确保(例如使用 static-assert 或 enable_if),只允许 Callback&lt;FArgs...&gt;::Handle(使用任何 FArgs)作为 HCallback&lt;int&gt;::Handle 的例子应该编译,而 std::string 应该失败。

【问题讨论】:

标签: c++ c++11 variadic-templates static-assert enable-if


【解决方案1】:

Handle 类中定义一个类型,并在makeTypeErasedCallbackHandle() 中引用该类型:

#include <memory>
#include <string>

template <typename... FArgs>
struct Callback {
    struct Handle {
        using callback_type = Callback<FArgs...>;    
    };
};

struct BaseCallbackHandle {
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
    using callback_type = typename H::callback_type;

    return {};
}

int main() {
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error
}

Live example

对于任何未定义嵌套类型的H,这将在实例化期间失败。


稍加努力,您就可以static_assert 为客户端生成有意义的消息,同时通过类型特征增加解决方案的灵活性。这样做的好处是callback_impl::is_callback 可以专门用于任意句柄类型:

#include <memory>
#include <string>

namespace callback_impl {

struct callback_identification_type {};

template <typename T, typename = void>
struct is_callback : std::false_type {};

template <typename T>
struct is_callback<T,
    std::enable_if_t<std::is_same<typename T::callback_id_type,
                     callback_identification_type>::value>>
 : std::true_type {};

}

template <typename... FArgs>
struct Callback {
    struct Handle {
        using callback_id_type = callback_impl::callback_identification_type;    
    };
};

struct BaseCallbackHandle {
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
    static_assert(callback_impl::is_callback<H>::value,
                  "The handle type is not a member of a recognised Callback<T...>");
    return {};
}

int main() {
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error

    return 0;
}

Live example

输出:

g++ -std=c++14 -O2 -Wall -Wno-unused-local-typedefs -pedantic -pthread main.cpp && ./a.out
main.cpp: In instantiation of 'TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H) [with H = std::__cxx11::basic_string<char>; TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>]':
main.cpp:41:35:   required from here
main.cpp:32:5: error: static assertion failed: The handle type is not a member of a recognised Callback<T...>
     static_assert(callback_impl::is_callback<H>::value,
     ^~~~~~~~~~~~~

【讨论】:

    【解决方案2】:

    一种方法是传递一些额外的参数:

    template <typename... Pack> struct packer {};
    
    using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
    
    template <typename... T>
    TypeErasedCallbackHandle makeTypeErasedCallbackHandle(typename Callback<T...>::Handle h, T...)
    {
       return {};
    }
    
    template <typename... T>
    TypeErasedCallbackHandle makeTypeErasedCallbackHandle_2(typename Callback<T...>::Handle h, packer<T...>)
    {
       return {};
    }
    
    int main()
    {
        Callback<int>::Handle h;
        std::string s;
        makeTypeErasedCallbackHandle(h, 0); //should compile fine
        // OR
        makeTypeErasedCallbackHandle_2(h, packer<int>());
    
        //makeTypeErasedCallbackHandle(s); //should raise a compile error
    }
    

    这利用identity trick(Stephan T. Lavavej)进行类型推导。

    【讨论】:

      【解决方案3】:

      您可以为您的班级家庭制作虚构成员,并在替代方面调用它: http://coliru.stacked-crooked.com/a/d5738766fd7ac45f

      class Handle
      {
          public:
          static void doNothing(){}
      };
      

      ...

      TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
      {
         H::doNothing();
         return {};
      

      更标准的方法是使用类特征或静态断言,来自 BOOST 等库(可能其中一些已经是语言的一部分)。

      编辑: http://coliru.stacked-crooked.com/a/2a3adcb9d9dd274c 这种方式稍微好一点,用var代替call。

      【讨论】:

        【解决方案4】:
        class BaseCallback
        {
          ~BaseCallback() = 0;
        };
        
        template<typename... FArgs>
        class Callback : public BaseCallback
        {
          ~Callback(){...}
          ...
        };
        
        template<typename H>
        TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
        {
          static_assert(std::is_base_of<BaseCallback, H>::value, "Must use a callback");
          return {};
        }
        

        这个应该工作并且只允许你在回调中使用那个函数

        【讨论】:

          猜你喜欢
          • 2021-10-28
          • 2023-04-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多