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

3

10

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.

Equiangular answered 13/2, 2018 at 10:9 Comment(0)
L
14

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";
}
Lifeordeath answered 13/2, 2018 at 10:18 Comment(6)
+ for the C++17 fold expressionNiggerhead
Fold expressions are bugged in VS2017 as of 15.7.5, so the alternate solution is necessary, minus the reimplementation of std::conjunction.Stylographic
@VittorioRomeo The example using std::conjunction has a slight bug - ::value is already taken by conjunction. I.e., it should be enable_if<conjunction_v<is_same<U, float>...>, void>Stylographic
How would this work if I can't use a return type? For constructors for example?Ines
@katrasnikj: it doesn't matter where this is, as long as it's part of the signature. It can be a default template parameter or default function parameter.Lifeordeath
@VittorioRomeo ah yes, I see. I was having problems with the fold expression version. It works with std::conjuction. Thank you.Ines
C
3

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
Cobos answered 13/2, 2018 at 10:36 Comment(0)
T
2

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.

Tonyatonye answered 13/2, 2018 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.