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
函数通过组合前面的 Foo
和 Bar
条件接受 int
s 和 float
s 但没有别的。
注意,我想避免改变之前的条件,我想按原样组合它们。任何 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);
}
你正在做货物崇拜 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
,您的默认类型参数会导致替换失败。如果 T
是 int
,则第二个类型参数的默认值为 void
.
template <typename T,
// This does not work
typename = std::enable_if_t<Bar<T>::value || Foo<T>::value>>
void FooOrBarIt(T t) {}
当然不是。
类型void
上没有::value
。 Bar<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
}
以这种方式组合概念要容易得多。
我正在努力寻找允许多个 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
函数通过组合前面的 Foo
和 Bar
条件接受 int
s 和 float
s 但没有别的。
注意,我想避免改变之前的条件,我想按原样组合它们。任何 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);
}
你正在做货物崇拜 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
,您的默认类型参数会导致替换失败。如果 T
是 int
,则第二个类型参数的默认值为 void
.
template <typename T,
// This does not work
typename = std::enable_if_t<Bar<T>::value || Foo<T>::value>>
void FooOrBarIt(T t) {}
当然不是。
类型void
上没有::value
。 Bar<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
}
以这种方式组合概念要容易得多。