These two non-variadic function templates do compile:
template <typename T, typename U>
typename std::enable_if<std::is_same<U, int>::value, void>::
type testFunction(T a, U b) {
std::cout << "b is integer\n";
}
template <typename T, typename U>
typename std::enable_if<std::is_same<U, float>::value, void>::
type testFunction(T a, U b) {
std::cout << "b is float\n";
}
however, similar variadic templates do not compile:
template <typename T, typename... U>
typename std::enable_if<std::is_same<U, int>::value, void>::
type testFunction(T a, U... bs) {
std::cout << "bs are integers\n";
}
template <typename T, typename... U>
typename std::enable_if<std::is_same<U, float>::value, void>::
type testFunction(T a, U... bs) {
std::cout << "bs are floats\n";
}
Maybe I am trying to do something that cannot be done. I know that similar functionality can be achieved using initializer lists, but I would like to avoid curly brackets required for initializer list arguments.
Yes. You can use a fold expression in C++17:
template <typename T, typename... U>
typename std::enable_if<(std::is_same<U, float>::value && ...), void>::
type testFunction(T a, U... bs) {
std::cout << "bs are floats\n";
}
In C++11, you can reimplement std::conjunction
:
template<class...> struct conjunction : std::true_type { };
template<class B1> struct conjunction<B1> : B1 { };
template<class B1, class... Bn>
struct conjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};
template <typename T, typename... U>
typename std::enable_if<
std::conjunction_v<std::is_same<U, float>...>,
void
>::type testFunction(T a, U... bs) {
std::cout << "bs are floats\n";
}
Or you could simply make use of additional variadic pack
to test if all template arguments are the same and equal to a given type (C++11):
#include <type_traits>
#include <iostream>
template <class...>
struct pack { };
template <typename T, typename... U>
typename std::enable_if<std::is_same<pack<int, U...>, pack<U..., int>>::value, void>::
type testFunction(T a, U... bs) {
std::cout << "bs are integers\n";
}
template <typename T, typename... U>
typename std::enable_if<std::is_same<pack<float, U...>, pack<U..., float>>::value, void>::
type testFunction(T a, U... bs) {
std::cout << "bs are floats\n";
}
int main() {
testFunction(1, 2, 3, 4, 5);
testFunction(1, 2.0f, 3.5f, 4.4f, 5.3f);
}
[live demo]
Output:
bs are integers
bs are floats
If you are bound to C++ 11 and want to keep your code readable, you can implement simple equivalent of is_same
which matches multiple types:
template <typename Ref, typename T1, typename... TN>
struct all_match;
template <typename Ref, typename T>
struct all_match<Ref,T>
{
static constexpr bool value = std::is_same<T,Ref>::value;
};
template <typename Ref, typename T1, typename... TN>
struct all_match
{
static constexpr bool value = std::is_same<T1,Ref>::value && all_match<Ref, TN...>::value;
};
And then (note, that the reference type goes first):
template <typename T, typename... U>
typename std::enable_if<all_match<int, U...>::value, void>::
type testFunction(T a, U... bs) {
std::cout << "bs are integers\n";
}
Live demo: click.
C++ 17 introduces fold expression which allows you to fold parameter pack (...
) over a binary operator. You can use it to easy apply std::is_same<>
for all types in a parameter pack and then and
the values:
template <typename T, typename... U>
typename std::enable_if<(std::is_same<U, float>::value && ...), void>::
type testFunction(T a, U... bs) {
std::cout << "bs are floats\n";
}
template <typename T, typename... U>
typename std::enable_if<(std::is_same<U, int>::value && ...), void>::
type testFunction(T a, U... bs) {
std::cout << "bs are ints\n";
}
You can check demo for this version here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With