I am struggling to find the correct syntax for allowing multiple OR'ed std::enable_if
conditions for my template functions.
#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.
Note that I want to avoid changing the previous conditions, I want to combine them as is. Anything C++17 is fine.
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.
You are trying to use enable_if_t
inside of enable_if_t
, which is not what you need. You need to use is_same_v
inside of 1 enable_if_t
, eg:
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:
#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. You should look to use std::disjunction
and its counterpart std::conjunction
to combine logical tests for SFINAE. These where introduced with C++17, so you should be able to use them with the setup you have.
Your ||
operator works on bool
types.
The bool
types you are looking for already exist in your code. std::is_same_v
is always a 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.
You are doing cargo-cult 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.
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. I mean, last I checked, the fact it worked with template class specialization isn't actually in the standard.
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.
// 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.
What is going on here is that your default type argument produces a substitution failure if T
is not an int
. If T
is an int
, the default value for the 2nd type argument is 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
. Bar<T>
is either void
or a substitution failure, and the same for Foo<T>
.
Neither supports ::value
. So you get an error.
Your Foo<T>
and Bar<T>
are not a great pattern. 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.
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
. 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.
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; =false
would produce the exact same results.
For the sake of providing a contrast, a C++20 version:
#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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.