15

Since there is a restriction on allowed non-type variadic templates, I am trying to write a function taking an arbitrary number of doubles using enable_if. In essence, I want to do something like:

    template<typename... T,
    typename = typename std::enable_if<std::is_convertible<T, double>::value, T>::type>
    foo(T... t){ /* code here */ }

I'm opting to put the enable_if as a default value for an unnamed parameter since my function is actually a constructor and will not have a return value. This would work for a single parameter, but as it's a variadic template T is a parameter pack, and the above code is not valid. So, how can I check every parameter is convertible to a double?

Community
  • 1
  • 1
user1997744
  • 411
  • 4
  • 16

5 Answers5

20

The bool_pack trick again.

template<bool...> struct bool_pack;
template<bool... bs> 
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;

Then

template<class R, class... Ts>
using are_all_convertible = all_true<std::is_convertible<Ts, R>::value...>;

and finally

template<typename... T,
typename = typename enable_if<are_all_convertible<double, T...>::value>::type>
foo(T... t){ /* code here */}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Neat, but doesn't have an obvious generalisation to "any are true"? – Kerrek SB Apr 16 '15 at 10:30
  • 4
    @KerrekSB "any are true" = not "all false". So just define `all_false` along similar lines and do `template using any_true = std::integral_constant::value>;` – T.C. Apr 16 '15 at 10:32
  • +1 Because you can change std::is_convertible to any type trait easily, making it a really universal solution. – plasmacel Mar 15 '16 at 02:52
8

You could use fold expression in c++17 to do the same thing as other answers posted here but without the hassle of creating templates.

#include <type_traits>

template <typename... T, typename = 
    typename std::enable_if<
        (true && ... && std::is_convertible_v<T, ___YOUR_TYPE___>),
        void
    >::type
>
constexpr auto foo(T...) noexcept {
        // your code 
}

And if you have access to C++20, you can use concepts:

#include <type_traits>

template <typename... T>
    requires(
        (... && std::is_convertible_v<T, ___YOUR_TYPE___>)
    )
constexpr auto foo(T...) noexcept {
        // your code 
}
The Moisrex
  • 1,857
  • 1
  • 14
  • 16
4

I think the simpler would be to use std::initializer_list:

foo(std::initializer_list<double> args)
{
    // Your stuff.
}

instead of variadic template. It may require to use {} instead of/ in addition to ()

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 3
    This can be annoying to use, because of the rule against narrowing conversions in `{}`. – T.C. Apr 16 '15 at 10:30
  • I have a constructor for `initializer_list>` already, and adding another for the case `initializer_list` would be a bad choice for my interface because several different behaviours make sense for it. – user1997744 Apr 17 '15 at 06:50
1

Here is another () version (heavily inspired by the T.C.'s one above):

#include <type_traits>

template <typename To, typename From, typename... R>
struct are_all_convertible {
    constexpr static bool value = std::is_convertible<From,To>::value &&
                                  are_all_convertible<To,R...>::value;
};

template <typename To, typename From>
struct are_all_convertible<To,From> {
    constexpr static bool value = std::is_convertible<From,To>::value;
};

template<typename... T,
typename = typename std::enable_if<are_all_convertible<double, T...>::value>::type>
foo(T... t){ /* code here */}
0

Here is a generic approach – a TMP for binary folding, using C++14. First, let's define the basic combining operations:

#include <type_traits>

struct and_op
{
    using type = bool;
    using identity = std::true_type;
    template <bool A, bool B> static constexpr bool value = A && B;
};

struct or_op
{
    using type = bool;
    using identity = std::false_type;
    template <bool A, bool B> static constexpr bool value = A || B;
};

Now the actual fold mechanic:

template <typename Op, typename Op::type...>
struct fold;

template <typename Op>
struct fold<Op> : Op::identity {};

template <typename Op, typename Op::type Val>
struct fold<Op, Val>
    : std::integral_constant<typename Op::type
    , Val> {};

template <typename Op, typename Op::type Val, typename Op::type... Tail>
struct fold<Op, Val, Tail...>
    : std::integral_constant<typename Op::type
    , Op::template value<Val, fold<Op, Tail...>::value>> {};

Next, we need a way to create unary traits from binary traits by binding:

template <template <typename, typename> class BPred, typename T>
struct bind_pred
{
    template <typename U>
    struct pred_1st : std::integral_constant<bool, BPred<T, U>::value> {};
    template <typename U>
    struct pred_2nd : std::integral_constant<bool, BPred<U, T>::value> {};
};

Finally, a helper wrapper to combine the result of applying a unary predicate:

template <typename Op, template <typename> class UPred, typename ...Args>
struct fold_pred : fold<Op, UPred<Args>::value...> {};

That's it. Now let's get to work:

template <typename T>
using maybe_double = bind_pred<std::is_convertible, double>::pred_2nd<T>;

#include <iomanip>
#include <iostream>

int main()
{
    std::cout
        << std::boolalpha
        << fold_pred<and_op, maybe_double, int, float>::value << '\n'
        << fold_pred<and_op, maybe_double, int, float, void>::value << '\n';
}

In C++17 (or C++1z, rather), you can write direct solutions with less code thanks to the new fold expressions. For example:

template <template <typename> class UPred, typename ...Args>
static constexpr bool pred_all = (UPred<Args>::value && ...);
//                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ unary fold

Usage:

static_assert(pred_all<maybe_double, int, float>);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084