std::enable_if 和 std::is_arithmetic 作为模板参数的问题

Troubles with std::enable_if and std::is_arithmetic as template parameter

我正在尝试实现一个 OutputArchive 模板 class,它有一个模板函数 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 的方式有关

因此,为了使其正常工作,您应该在 2 个案例中使用 std::enable_if。 我将展示 return 类型的示例,但使用模板参数也可以。

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
    }
}

这应该可以解决问题

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.

你的代码有问题。

排名不分先后

1) 不是错误(我想)但是...使用 typename std::enable_if<...>::type 或者,从 C++14 开始,std::enable_if_t<...>;无需在 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 enable/disable return 值(参见 Igor 或 Marek R 的答案)或者您可以写成

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

变成

 template <typename T, void * = nullptr>

有意义,作为签名,并且有效

3) 正如评论中指出的那样,你应该使用 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() 冲突,因为对于算术值,两个版本都匹配,编译器不能 select一个接一个。

我可以提出两个解决方案

(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");
 }