【问题标题】:Troubles with std::enable_if and std::is_arithmetic as template parameterstd::enable_if 和 std::is_arithmetic 作为模板参数的问题
【发布时间】:2018-10-13 21:41:14
【问题描述】:

我正在尝试实现一个OutputArchive 模板类,它有一个模板函数processImpl()。看起来像这样:

template<typename ArchiveType>
class OutputArchive {
    ...

    template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>>> inline
    ArchiveType& processImpl(Type&& type) {
        // Implementation
    }

    template<typename Type, typename = void> inline
    ArchiveType& processImpl(Type&& type) {
        // Implementation
    }
}

这里的想法是,如果我将charintfloat 等传递给我的processImpl() 函数,则应该使用第一个重载;然而,事实并非如此。第二个重载似乎总是被使用,我完全不知道我可能做错了什么。我想这确实与我使用 std::enable_if 的方式有关

【问题讨论】:

  • 尝试同时使用std::remove_cvstd::remove_reference
  • 我建议使用std::decay 而不是std::remove...
  • std::decaystd::remove_cvstd::remove_reference 都没有成功。编辑:我假设它们的用法如下:..std::is_arithmetic_v&lt;std::decay&lt;Type&gt;&gt;&gt;
  • 除了这个问题 - 为什么不使用 constexpr-if 的单个函数?
  • 我是 SFINAE 和模板函数重载的新手,但我仍在努力思考它。我什至不确定我是否完全理解你的问题。您是在谈论制作人们使用的模板结构之一,例如,检查一个函数是否存在于一个类中?

标签: c++ templates c++14 sfinae overloading


【解决方案1】:

因此,要使其正常工作,您应该在 2 种情况下使用 std::enable_if。 我将展示一个返回类型的示例,但使用模板参数也可以。

template<typename Type> inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
    // Implementation
}

template<typename Type> inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
    // Implementation
}

注意第二种情况下的否定。

但从 C++17 开始,更好的方法是使用 constexpr:

ArchiveType& processImpl(Type&& type) {
    if constexpr(std::is_arithmetic_v<type>) {
        // implementation
    } else {
        // implementation
    }
}

【讨论】:

    【解决方案2】:

    你的代码有问题。

    排名不分先后

    1) 不是错误(我想)但是...使用 typename std::enable_if&lt;...&gt;::type 或者,从 C++14 开始,std::enable_if_t&lt;...&gt;;无需在std::enable_if_t 之前使用typename

    2)如果要在参数类型列表中使用std::enable_if,这个不行

     template <typename T, std::enable_if_t<(test with T)>>
    

    因为,如果T 的测试为真,则变为

     template <typename T, void>
    

    作为模板函数的签名没有意义。

    您可以 SFINAE 启用/禁用返回值(请参阅 Igor 或 Marek R 的答案),或者您可以改为编写

     template <typename T, std::enable_if_t<(test with T)> * = nullptr>
    

    变成了

     template <typename T, void * = nullptr>
    

    有意义,作为签名,有效

    3) 在 cmets 中指出,您应该使用 std::remove_reference,所以

       template <typename Type,
                 std::enable_if_t<std::is_arithmetic_v<
                    std::remove_reference_t<Type>>> * = nullptr> inline
       ArchiveType & processImpl (Type && type)
    

    现在这个函数应该拦截算术值但是......

    4) 对于算术值,前面的 processImpl() 与另一个 processImpl() 发生冲突,因为在算术值的情况下,两个版本都匹配并且编译器无法选择一个。

    我可以提出两种解决方案

    (a) 通过 SFINAE 禁用算术案例中的第二个版本;我的意思是,写第二个如下

       template <typename Type,
                 std::enable_if_t<false == std::is_arithmetic_v<
                    std::remove_reference_t<Type>>> * = nullptr> inline
        ArchiveType & processImpl (Type && type)
    

    (b) 传递一个中间函数,该函数发送一个额外的int 值并在算术版本中接收int,在泛型版本中接收long;我的意思是

       template <typename Type,
                 std::enable_if_t<std::is_arithmetic_v<
                      std::remove_reference_t<Type>>> * = nullptr> inline
       ArchiveType & processImpl (Type && type, int)
        { /* ... */ }
    
       template <typename Type>
       ArchiveType & processImpl (Type && type, long)
        { /* ... */ }
    
       template <typename Type>
       ArchiveType & processImpl (Type && type)
        { return processImpl(type, 0); }
    

    这样算术版本,准确接收int,比通用版本更受欢迎(启用时);否则使用通用版本。

    以下是基于 (b) 解决方案的完整 C++14 示例

    #include <iostream>
    #include <type_traits>
    
    template <typename ArchiveType>
    struct OutputArchive
     {
       ArchiveType  value {};
    
       template <typename Type,
                 std::enable_if_t<std::is_arithmetic_v<
                    std::remove_reference_t<Type>>> * = nullptr> inline
       ArchiveType & processImpl (Type && type, int)
        {
          std::cout << "--- processImpl aritmetic: " << type << std::endl;
    
          return value;
        }
    
       template <typename Type>
       ArchiveType & processImpl (Type && type, long)
        {
          std::cout << "--- processImpl generic: " << type << std::endl;
    
          return value;
        }
    
       template <typename Type>
       ArchiveType & processImpl (Type && type)
        { return processImpl(type, 0); }
     };
    
    int main()
     {
       OutputArchive<int>  oa;
    
       long  l{2l};
       oa.processImpl(l);
       oa.processImpl(3);
       oa.processImpl("abc");
     }
    

    【讨论】:

    • +1 以获得非常深入的解释;然而,Igor 的回应被接受了,因为它简短而甜美,并展示了一种更现代的方法 (C++17)
    【解决方案3】:

    这应该可以解决问题

    template<typename ArchiveType>
    class OutputArchive {
        ...
        template<typename Type>
        inline
        typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&>
        processImpl(Type type) {
            // Implementation
        }
    
        template<typename Type>
        inline
        typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&>
        processImpl(Type&& type) {
            // Implementation
        }
    };
    

    Live sample.

    【讨论】:

      猜你喜欢
      • 2012-06-18
      • 1970-01-01
      • 2022-08-18
      • 1970-01-01
      • 1970-01-01
      • 2017-05-29
      • 2012-04-05
      • 1970-01-01
      • 2015-11-17
      相关资源
      最近更新 更多