【问题标题】:Partial template function specialization with enable_if: make default implementation带有 enable_if 的部分模板函数特化:默认实现
【发布时间】:2017-06-16 09:19:27
【问题描述】:

使用 C++11 的 enable_if 我想为一个函数定义几个专门的实现(例如,基于参数的类型)以及一个默认实现。正确的定义方式是什么?

以下示例无法按预期工作,因为调用了“通用”实现,无论T 是什么类型。

#include <iostream>

template<typename T, typename Enable = void>
void dummy(T t)
{
  std::cout << "Generic: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
  std::cout << "Integral: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
  std::cout << "Floating point: " << t << std::endl;
}

int main() {
  dummy(5); // Print "Generic: 5"
  dummy(5.); // Print "Generic: 5"
}

在我的最小示例中,一个解决方案是明确声明“通用”实现不适用于整数或浮点类型,使用

std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type

这正是我想要避免的,因为在我的实际用例中有很多专门的实现,我想避免默认实现的时间很长(容易出错!)。

【问题讨论】:

    标签: c++ c++11 sfinae enable-if


    【解决方案1】:

    您可以引入rank 来优先处理您的一些重载:

    template <unsigned int N>
    struct rank : rank<N - 1> { };
    
    template <>
    struct rank<0> { };
    

    然后您可以像这样定义 dummy 重载:

    template<typename T>
    void dummy(T t, rank<0>)
    {
        std::cout << "Generic: " << t << std::endl;
    }
    
    template<typename T, 
             typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
    void dummy(T t, rank<1>)
    {
        std::cout << "Integral: " << t << std::endl;
    }
    
    template<typename T, 
             typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
    void dummy(T t, rank<1>)
    {
        std::cout << "Floating point: " << t << std::endl;
    }
    

    然后,您可以将呼叫隐藏在dispatch 后面:

    template <typename T>
    void dispatch(T t)
    {
       return dummy(t, rank<1>{});
    }
    

    用法:

    int main() 
    {
        dispatch(5);    // Print "Integral: 5"
        dispatch(5.);   // Print "Floating point: 5"
        dispatch("hi"); // Print "Generic: hi"
    }
    

    live example on wandbox


    说明:

    使用rank 引入了“优先级”,因为当X &gt; Y 时,需要隐式转换才能将rank&lt;X&gt; 转换为rank&lt;Y&gt;dispatch 首先尝试用rank&lt;1&gt; 调用dummy,优先考虑您的约束重载。如果enable_if 失败,rank&lt;1&gt; 会隐式转换为rank&lt;0&gt; 并进入“回退”情况。


    奖励:这是一个使用 if constexpr(...) 的 C++17 实现。

    template<typename T>
    void dummy(T t)
    {
        if constexpr(std::is_integral_v<T>)
        {
            std::cout << "Integral: " << t << std::endl;
        }
        else if constexpr(std::is_floating_point_v<T>)
        {
            std::cout << "Floating point: " << t << std::endl;
        }
        else
        {
            std::cout << "Generic: " << t << std::endl;
        }
    }
    

    live example on wandbox

    【讨论】:

    • @Bruno 我不同意,我认为这个解决方案并没有表达调度到正确方法的意图。几个月后阅读这段代码;虽然很简单,但您可能会想知道rank 的含义。
    【解决方案2】:

    函数不能部分特化。我假设您想要做的是更喜欢那些包含显式条件的重载?实现此目的的一种方法是在 default 函数的声明中使用可变参数省略号,因为省略号函数在重载解决顺序中的优先级较低:

    #include <iostream>
    
    template<typename T>
    void dummy_impl(T t, ...)
    {
      std::cout << "Generic: " << t << std::endl;
    }
    
    
    template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
    void dummy_impl(T t, int)
    {
      std::cout << "Integral: " << t << std::endl;
    }
    
    
    template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
    void dummy_impl(T t, int)
    {
      std::cout << "Floating point: " << t << std::endl;
    }
    
    template <class T>
    void dummy(T t) {
       dummy_impl(t, int{});
    }
    
    int main() {
      dummy(5); 
      dummy(5.); 
      dummy("abc"); 
    }
    

    输出:

    Integral: 5
    Floating point: 5
    Generic: abc
    

    [live demo]

    @doublep 在评论中提到的另一个选项是使用结构来实现您的功能,然后对其进行部分专门化。

    【讨论】:

    • 另一种选择是将实现放入一个虚拟类/结构中并对其进行部分专门化。虽然它可能涉及更多的打字。
    • 我注意到确实需要第二个模板参数是一个指针,nullptr 作为默认值才能使构造工作。你能解释一下为什么会这样,或者给我一些解释吗?谢谢!
    • @doublep 更多的输入是完全可以接受的,如果这意味着每个人都可以阅读和维护您的代码。绝对值得付出努力。
    • @Bruno 我使用它来强制编译器实例化非类型模板参数的类型。如果没有这样的实例化,sfinae 将不会被触发。
    • @misterwhy 使用可变参数省略以及 VittorioRomeo 提出的优先级标签调度,所有这些都隐藏在例如实现命名空间是众所周知的并且已建立的机制来处理 sfinae。更重要的是,在 c++11 出现之前,可变参数省略是执行 sfinae 的极少数帮手之一。
    【解决方案3】:

    我会像这样使用标签调度:

    namespace Details
    {
        namespace SupportedTypes
        {
            struct Integral {};
            struct FloatingPoint {};
            struct Generic {};
        };
    
    
        template <typename T, typename = void>
        struct GetSupportedType
        {
            typedef SupportedTypes::Generic Type;
        };
    
        template <typename T>
        struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
        {
            typedef SupportedTypes::Integral Type;
        };
    
        template <typename T>
        struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
        {
            typedef SupportedTypes::FloatingPoint Type;
        };
    
        template <typename T>
        void dummy(T t, SupportedTypes::Generic)
        {
            std::cout << "Generic: " << t << std::endl;
        }
    
        template <typename T>
        void dummy(T t, SupportedTypes::Integral)
        {
            std::cout << "Integral: " << t << std::endl;
        }
    
        template <typename T>
        void dummy(T t, SupportedTypes::FloatingPoint)
        {
            std::cout << "Floating point: " << t << std::endl;
        }
    } // namespace Details
    

    然后像这样隐藏样板代码:

    template <typename T>
    void dummy(T t)
    {
        typedef typename Details::GetSupportedType< T >::Type SupportedType;
        Details::dummy(t, SupportedType());
    }
    

    GetSupportedType 为您提供了一种猜测您将要使用的实际类型的中心方法,即您每次添加新类型时要专门化的类型。

    然后您只需通过提供正确的标签实例来调用正确的dummy 重载。

    最后,调用dummy

    dummy(5); // Print "Generic: 5"
    dummy(5.); // Print "Floating point: 5"
    dummy("lol"); // Print "Generic: lol"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多