[英]std::enable_if with multiple or-conditions
I am struggling to find the correct syntax for allowing multiple OR'ed std::enable_if
conditions for my template functions.我正在努力寻找允许多个 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);
}
I want the FooOrBarIt
function to accept both int
s and float
s but nothing else by combining the previous Foo
and Bar
conditions.我希望
FooOrBarIt
function 通过结合前面的Foo
和Bar
条件接受int
s 和float
s 但没有别的。
Note that I want to avoid changing the previous conditions, I want to combine them as is.请注意,我想避免改变以前的条件,我想按原样组合它们。 Anything C++17 is fine.
任何 C++17 都可以。
The current code fails with compile errors like:当前代码因编译错误而失败,例如:
candidate template ignored: requirement 'std::is_same_v<int, float>' was not satisfied [with T = int]
in clang.在 clang。
You are trying to use enable_if_t
inside of enable_if_t
, which is not what you need.您正在尝试在
enable_if_t
内部使用enable_if_t
,这不是您所需要的。 You need to use is_same_v
inside of 1 enable_if_t
, eg:您需要在 1
enable_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>>>
So adjust your using
statements accordingly, eg:因此,相应地调整您的
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 doesn't work quiet as simply with logical operators. SFINAE 不像逻辑运算符那样安静地工作。 You should look to use
std::disjunction
and its counterpart std::conjunction
to combine logical tests for SFINAE.您应该使用
std::disjunction
及其对应的std::conjunction
来组合 SFINAE 的逻辑测试。 These where introduced with C++17, so you should be able to use them with the setup you have.这些在 C++17 中引入,因此您应该能够将它们与您的设置一起使用。
Your ||
你的
||
operator works on bool
types.运算符适用于
bool
类型。
The bool
types you are looking for already exist in your code.您要查找的
bool
类型已存在于您的代码中。 std::is_same_v
is always a 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
Consider that an expression containing both Foo<T>
and Bar<T>
can never be well-defined.考虑到同时包含
Foo<T>
和Bar<T>
的表达式永远无法明确定义。
You are doing cargo-cult SFINAE.你正在做货物崇拜 SFINAE。 By this, I mean you are using SFINAE patterns to pull off what you need, and not understanding why you are doing those patterns.
通过这个,我的意思是你正在使用 SFINAE 模式来实现你需要的东西,而不是理解你为什么要做这些模式。
This usually isn't all that bad.这通常并没有那么糟糕。 SFINAE is sort of insane, as an accidental feature that leaked into the language and gave us Turing-complete overload resolution.
SFINAE 有点疯狂,因为它是一种偶然的特性,它泄漏到语言中并为我们提供了图灵完备的重载解决方案。 I mean, last I checked, the fact it worked with template class specialization isn't actually in the standard.
我的意思是,上次我检查过,它与模板 class 专业化一起工作的事实实际上并不在标准中。
So following existing patterns to make it look reasonable is a good plan.因此,遵循现有模式使其看起来合理是一个很好的计划。
When you want to do something new, however, you have to understand what is actually going on.然而,当你想做一些新的事情时,你必须了解实际发生的事情。
template <typename T>
using Foo = std::enable_if_t<std::is_same_v<T, int>>;
this is a template alias that results in the type void
if T
passes the test, and in a substitution failure if T
fails the test.这是一个模板别名,如果
T
通过测试,则返回类型void
,如果T
未通过测试,则返回类型失败。
// Fine - accepts only ints
template <typename T, typename = Foo<T>>
void FooIt(T t) {}
your comment is a bit wrong.你的评论有点不对。
FooIt<double, void>
works fine here. FooIt<double, void>
在这里工作正常。
What is going on here is that your default type argument produces a substitution failure if T
is not an int
.这里发生的事情是,如果
T
不是int
,您的默认类型参数会产生替换失败。 If T
is an int
, the default value for the 2nd type argument is void
.如果
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) {}
of course it doesn't.当然不是。
There is no ::value
on the type void
.类型
void
上没有::value
。 Bar<T>
is either void
or a substitution failure, and the same for Foo<T>
. Bar<T>
void
或替换失败,对于Foo<T>
也是如此。
Neither supports ::value
.都不支持
::value
。 So you get an error.所以你得到一个错误。
Your Foo<T>
and Bar<T>
are not a great pattern.您的
Foo<T>
和Bar<T>
不是很好的模式。 But you stated you want to keep them.但是你说你想保留它们。
So what I'd start with is this:所以我要开始的是:
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;
now test_t
can be used to fuze your traits.现在
test_t
可以用来引信你的特征。
template <typename T,
typename = test_t<T, Bar, Foo>>
void FooOrBarIt(T t) {}
in a way you are familiar with.以你熟悉的方式。
Note that this style of SFINAE has problems where you can't use it to dispatch between two overloads of FooOrBarIt
.请注意,这种风格的 SFINAE 存在一些问题,您无法使用它在
FooOrBarIt
的两个重载之间进行调度。 Ie, IE,
template<class T, class=Bar<T>>
void bob(T) {}
template<class T, class=Foo<T>>
void bob(T) {}
this results in an overload resolution error.这会导致重载解析错误。
Another cargo-cult pattern:另一种货物崇拜模式:
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) {}
looks slightly different, but permits the two bob
overloads above to work.看起来略有不同,但允许上面的两个
bob
重载工作。
For that variant, you'd have to change how you joined them:对于该变体,您必须更改加入它们的方式:
template<Ts...>
using AllTraits = bool;
template <typename T,
AllTraits<Foo<T>, Bar<T>> = true>
void FooOrBarIt(T t) {}
note that the =true
in this particular cargo cult version is not a test;请注意,这个特定的 cargo cult 版本中的
=true
不是测试; =false
would produce the exact same results. =false
会产生完全相同的结果。
For the sake of providing a contrast, a C++20 version:为了提供对比,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
}
Concepts are a lot easier to combine in this fashion.以这种方式组合概念要容易得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.