std::enable_if 有多个 or 条件

std::enable_if with multiple or-conditions

我正在努力寻找允许多个 OR'ed std::enable_if 条件用于我的模板函数的正确语法。

#include <type_traits>

template <typename T>
using Foo = std::enable_if_t<std::is_same_v<T, int>>;

template <typename T>
using Bar = std::enable_if_t<std::is_same_v<T, float>>;

// Fine - accepts only ints
template <typename T, typename = Foo<T>>
void FooIt(T t) {}

// Fine - accepts only floats
template <typename T, typename = Bar<T>>
void BarIt(T t) {}

template <typename T,
          // This does not work
          typename = std::enable_if_t<Bar<T>::value || Foo<T>::value>>
void FooOrBarIt(T t) {}

int main() {
  FooIt(1);
  BarIt(1.0f);

  // FooIt(1.0f);  // OK - No float version of FooIt
  // BarIt(1);     // OK - No int version of BarIt

  // Not OK - Both fails to compile
  // FooOrBarIt(1);
  // FooOrBarIt(1.0f);
}

我希望 FooOrBarIt 函数通过组合前面的 FooBar 条件接受 ints 和 floats 但没有别的。

注意,我想避免改变之前的条件,我想按原样组合它们。任何 C++17 都可以。

当前代码失败,编译错误如下:

candidate template ignored: requirement 'std::is_same_v<int, float>' was not satisfied [with T = int]

叮当响。

SFINAE 不像逻辑运算符那样安静地工作。您应该使用 std::disjunction 及其对应的 std::conjunction 来组合 SFINAE 的逻辑测试。这些是在 C++17 中引入的,所以你应该能够在你的设置中使用它们。

您的 || 运算符适用于 bool 类型。

您要查找的 bool 类型已存在于您的代码中。 std::is_same_v 总是 bool.

template <typename T, typename = std::enable_if_t<
           std::is_same_v<T,int> || std::is_same_v<T,float>
         >>* = nullptr

考虑到包含 Foo<T>Bar<T> 的表达式永远无法明确定义。

您正试图在 enable_if_t 内部使用 enable_if_t,这不是您需要的。需要在1enable_if_t里面使用is_same_v,例如:

template <typename T,
          typename = std::enable_if_t<std::is_same_v<T,float> || std::is_same_v<T,int>>>

因此请相应地调整您的 using 语句,例如:

#include <type_traits>

template <typename T>
inline constexpr bool Is_Int = std::is_same_v<T, int>;

template <typename T>
using Enable_If_Int = std::enable_if_t<Is_Int<T>>;

template <typename T>
inline constexpr bool Is_Float = std::is_same_v<T, float>;

template <typename T>
using Enable_If_Float = std::enable_if_t<Is_Float<T>>;

// Fine - accepts only ints
template <typename T, typename = Enable_If_Int<T>>
void FooIt(T t) {}

// Fine - accepts only floats
template <typename T, typename = Enable_If_Float<T>>
void BarIt(T t) {}

template <typename T,
          typename = std::enable_if_t<Is_Float<T> || Is_Int<T>>>
void FooOrBarIt(T t) {}

int main() {
    FooIt(1);
    BarIt(1.0f);

    // FooIt(1.0f);  // OK - No float version of FooIt
    // BarIt(1);     // OK - No int version of BarIt

    // OK - Both compile fine
    FooOrBarIt(1);
    FooOrBarIt(1.0f);
}

Online Demo

你正在做货物崇拜 SFINAE。通过这个,我的意思是你正在使用 SFINAE 模式来实现你需要的东西,而不是理解你为什么要这样做。

这通常并没有那么糟糕。 SFINAE 有点疯狂,因为它是一种偶然的特性,它泄漏到语言中并为我们提供了图灵完备的重载解决方案。我的意思是,最后我检查了一下,它与模板 class 专业化一起工作的事实实际上并不在标准中。

因此遵循现有模式使其看起来合理是一个很好的计划。

然而,当你想做一些新的事情时,你必须了解实际发生的事情。

template <typename T>
using Foo = std::enable_if_t<std::is_same_v<T, int>>;

这是一个模板别名,如果 T 通过测试,则生成类型 void,如果 T 未通过测试,则生成替换失败。

// Fine - accepts only ints
template <typename T, typename = Foo<T>>
void FooIt(T t) {}

你的评论有点错误。 FooIt<double, void> 在这里工作正常。

这里发生的事情是,如果 T 不是 int,您的默认类型参数会导致替换失败。如果 Tint,则第二个类型参数的默认值为 void.

template <typename T,
      // This does not work
      typename = std::enable_if_t<Bar<T>::value ||  Foo<T>::value>>
void FooOrBarIt(T t) {}

当然不是。

类型void上没有::valueBar<T>void 或替换失败,Foo<T> 也是如此。

都不支持::value。所以你得到一个错误。

您的 Foo<T>Bar<T> 不是很好的模式。但是你说你想保留它们。

所以我要开始的是:

template<class T, template<class>class Z, class=void, template<class>class...Zs>
struct test{};
template<class T, template<class>class Z>
struct test<T,Z,Z<T>>{
  using type=void;
};
template<class T,
  template<class>class Z0,
  template<class>class Z1, 
  template<class>class...Zs
>
struct test<T, Z0, Z0<T>, Z1, Zs...>:
  test<T, Z1, void, Zs...>
{};
template<class T, template<class>class Z0, template<class>class...Zs>
using test_t = typename test<T, Z0, void, Zs...>::type;

现在 test_t 可以用来引信你的特质。

template <typename T,
      typename = test_t<T, Bar, Foo>>
void FooOrBarIt(T t) {}

以您熟悉的方式。

题外话

请注意,这种风格的 SFINAE 存在一些问题,您无法使用它在 FooOrBarIt 的两个重载之间进行调度。即,

template<class T, class=Bar<T>>
void bob(T) {}
template<class T, class=Foo<T>>
void bob(T) {}

这会导致重载解析错误。

另一种货物崇拜模式:

template<class T>
using Bar = std::enable_if_t<std::is_same_v<T,int>, bool>;
template<class T>
using Foo = std::enable_if_t<std::is_same_v<T,double>, bool>;

template<class T, Bar<T> =true>
void bob(T) {}
template<class T, Foo<T> =true>
void bob(T) {}

看起来略有不同,但允许上面的两个 bob 重载工作。

对于该变体,您必须更改加入它们的方式:

template<Ts...>
using AllTraits = bool;

template <typename T,
      AllTraits<Foo<T>, Bar<T>> = true>
void FooOrBarIt(T t) {}

请注意,此特定 cargo cult 版本中的 =true 不是测试; =false 会产生完全相同的结果。

为了对比,C++20版本:

#include <type_traits>

template<typename T>
concept Foo = std::is_same_v<T, int>;

template<typename T>
concept Bar = std::is_same_v<T, float>;

template<typename T>
concept FooOrBar = Foo<T> || Bar<T>;

template<Foo T>
void FooIt(T) { }

template<Bar T>
void BarIt(T) { }

template<FooOrBar T>
void FooOrBarIt(T) { }

int main() {
  FooIt(1); // OK
  BarIt(1.0f); // OK
  
  // FooIt(1.0f);  // OK - No float version of FooIt
  // BarIt(1);     // OK - No int version of BarIt


  FooOrBarIt(1); // OK
  FooOrBarIt(1.0f); // OK
  // FooOrBarIt(1.0); // No double version of FooOrBarIt
}

以这种方式组合概念要容易得多。