Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use 'enable_if' and 'is_same' with variadic function templates?

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.

like image 629
krsi Avatar asked Feb 13 '18 10:02

krsi


3 Answers

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";
}
like image 140
Vittorio Romeo Avatar answered Nov 16 '22 18:11

Vittorio Romeo


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
like image 33
W.F. Avatar answered Nov 16 '22 19:11

W.F.


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.

like image 2
Mateusz Grzejek Avatar answered Nov 16 '22 19:11

Mateusz Grzejek