enable_if with variadic template arguments

I have:
1
2
template <typename FIRST, typename... REST, typename std::enable_if<std::is_convertible<FIRST, Base*>::value>::type* = nullptr>
void foo (FIRST first, REST... rest) {}

that successfully allows me to enable the function foo() only if FIRST is convertible to Base*, but I also only want foo() enabled if each type in REST... meets the same condition. What is the syntax for that? If no such syntax exists, how to achieve that effect?
Won't foo be calling itself recursively to unpack rest...? That will do the validation for you. Just use a decltype in the trailing return type and have the base case have the actual return type (as well as perform the final check).
Last edited on
I'm trying to do something along the lines of what you said, but the following syntax is not allowed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename, typename...> struct fooHelper;

// enable_if here not permitted
template <typename FIRST, typename std::enable_if<std::is_convertible<FIRST, Base*>::value>::type* = nullptr*>
struct fooHelper<FIRST> {
	void operator() (FIRST first) {/* Do whatever */}
};

// enable_if here not permitted
template <typename FIRST, typename... REST, typename std::enable_if<std::is_convertible<FIRST, Base*>::value>::type* = nullptr> 
struct fooHelper {
	void operator() (FIRST first, REST... rest) {
		/* Do whatever */
		fooHelper<REST...>()(rest...);	
	}
};

template <typename FIRST, typename... REST, typename std::enable_if<std::is_convertible<FIRST, Base*>::value>::type* = nullptr>
void foo (FIRST first, REST... rest) {
	fooHelper<FIRST, REST...>()(first, rest...);
}

I don't know what decltype you are referring to when the return type is void.
Last edited on
Untested, but this is how I would do it:
1
2
3
4
5
6
7
8
9
10
11
12
template<typename Last>
auto foo(Last &&last)
-> typename std::enable_if<std::is_convertible<Last, Base *>::value, void>::type
{
    //...
}
template<typename First, typename... Rest>
auto foo(typename std::enable_if<std::is_convertible<First, Base *>, First>::type &&first, Rest &&... rest)
{
    //...
    return foo(rest...); //void returns are allowed in C++
}
JLBorges knows better than me though.
Last edited on
1
2
3
4
5
6
7
8
template<typename First, typename... Rest>
auto foo(typename std::enable_if<std::is_convertible<First, Base*>::value, First>::type &&first, Rest &&... rest)
     -> decltype( foo( std::forward<Rest>(rest)... ) ) // *** added
{
    //...
    foo( std::forward<First>(first) ) ; // *** added
    return foo( std::forward<Rest>(rest)... ); // *** std::forward added
}


This chases its own tail; the type First can't be deduced.
(To be able to deduce the type First, we need to know beforehand what First is).


Something like this, perhaps:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <type_traits>
#include <memory>

namespace utility
{
    template < typename, typename... > struct cnt_convertible ;
    template < typename A, typename B > struct cnt_convertible<A,B>
    { static constexpr std::size_t value = std::is_convertible<A,B>::value ; };
    template < typename A, typename FIRST, typename... REST > struct cnt_convertible<A,FIRST,REST...>
    { static constexpr std::size_t value = cnt_convertible<A,FIRST>::value + cnt_convertible<A,REST...>::value ; };

    template < bool CONDITION > struct true_if : std::conditional< CONDITION, std::true_type, std::false_type >::type {} ;

    template < typename... T > struct all_are_convertible : true_if< cnt_convertible<T...>::value == ( sizeof...(T) - 1 ) > {} ;
    template < typename... T > struct none_are_convertible : true_if< cnt_convertible<T...>::value == 0 > {} ;
    template < typename... T > struct few_are_convertible : true_if< !all_are_convertible<T...>::value &&
                                                                     !none_are_convertible<T...>::value > {};
    // etc.
}

template < typename... TYPES >
typename std::enable_if< utility::all_are_convertible<int,TYPES...>::value >::type foo( TYPES&&...)
{ std::cout << "all are implicitly convertible to int\n" ; }

template < typename... TYPES >
typename std::enable_if< utility::few_are_convertible<int,TYPES...>::value >::type foo( TYPES&&...)
{ std::cout << "some, but not all, are implicitly convertible to int\n" ; }

template < typename... TYPES >
typename std::enable_if< utility::none_are_convertible<int,TYPES...>::value >::type foo( TYPES&&...)
{ std::cout << "none are implicitly convertible to int\n" ; }

int main()
{
    foo( 'a', 56L, 75ULL, !std::addressof(std::cout), 0 ) ; // all are implicitly convertible to int
    foo( 'a', 56L, 75ULL, std::allocator_arg, 0, std::cout, 0 ) ; // some, but not all, are implicitly convertible to int
    foo( std::addressof(std::cout), std::allocator_arg, std::cout ) ; // none are implicitly convertible to int
}

http://coliru.stacked-crooked.com/a/81cde0037203d71b
> enable the function foo() only if FIRST is convertible to Base*,
> but I also only want foo() enabled if each type in REST... meets the same condition.

On re-reading, 'convertible to Base*' suggests that you are looking for an inheritance relationship, and that you expect FIRST, REST... to be pointers.

If that is the case, favour std::is_base_of<> over std::is_convertible<>.
(We wouldn't want some type with a non-explicit operator Base*() to trip us up.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <type_traits>

namespace utility
{
    template < typename... > struct are_derived_from ;

    template < typename BASE, typename T >
    struct are_derived_from<BASE,T> : std::is_base_of< BASE, typename std::decay<T>::type > {} ;

    template < typename BASE, typename FIRST, typename... REST >
    struct are_derived_from<BASE,FIRST,REST...>
                : std::conditional< std::is_base_of< BASE, typename std::decay<FIRST>::type >::value &&
                                    are_derived_from<BASE,REST...>::value,
                                    std::true_type, std::false_type >::type {} ;
}

struct base { /* ... */ };

template < typename FIRST, typename... REST >
typename std::enable_if< utility::are_derived_from<base,FIRST,REST...>::value >::type foo( FIRST*, REST*... )
{ std::cout << "all are pointers to types derived from base\n" ; }

template < typename FIRST, typename... REST >
typename std::enable_if< utility::are_derived_from<base,FIRST,REST...>::value >::type
foo( FIRST&&, REST&&... )
{ std::cout << "all are references to types derived from base\n" ; }

int main()
{
    struct A : base{} a ; struct B : base{} b ; struct C : base{} c ; struct D : base{} d ;
    foo( &a, &b, &c, &d ) ;
    foo( a, b, B(), c, d, D() ) ;
}

http://coliru.stacked-crooked.com/a/c08134984918c545
Yes, you're right about my intentions, but your first solution works perfectly anyway (but I know you are suggesting it may break down later on, so I will consider is_base_of).

As usual, I'm always eager to take JLBorges' idea and generalize further, even if I don't need it. The following below uses an extra template to generalize the condition to anything (though I'm not sure I did it in the best way, because the extra template argument is just int). Now though I don't need it, I'm curious as to how to specify the order of conditions among the arguments (instead of all the arguments satisfying the same condition). Anyways, here's my generalization so far (some of the functions tested):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <type_traits>
#include <string>
#include <memory>

namespace Utility {
  	enum UnaryPredicate {IsString, IsPointer};  // etc...

	template <int N, typename A> struct UnaryCondition;
 
  	template <typename A>
 	struct UnaryCondition<IsString, A> {static constexpr std::size_t value = std::is_same<A, std::string>::value;};

  	template <typename A>
 	struct UnaryCondition<IsPointer, A> {static constexpr std::size_t value = std::is_pointer<A>::value;};

    template <int, typename...> struct count_unary_true;
 	
    template <int N, typename A>
	struct count_unary_true<N, A> {
		static constexpr std::size_t value = UnaryCondition<N, A>::value;
	};
 
    template <int N, typename FIRST, typename... REST>
	struct count_unary_true<N, FIRST, REST...> {
		static constexpr std::size_t value = count_unary_true<N, FIRST>::value + count_unary_true<N, REST...>::value;
	};

//	--------------------------------------------------------------------------------------------------------------------------
	enum BinaryPredicate {IsConvertible, IsBaseOf};  // etc...
  
	template <int N, typename A, typename B> struct BinaryCondition;
 
  	template <typename A, typename B>
 	struct BinaryCondition<IsConvertible, A, B> {static constexpr std::size_t value = std::is_convertible<A,B>::value;};

 	template <typename A, typename B>
 	struct BinaryCondition<IsBaseOf, A, B> {static constexpr std::size_t value = std::is_base_of<A,B>::value;};
 
    template <int, typename, typename...> struct count_binary_true;
 	
    template <int N, typename A, typename B>
	struct count_binary_true<N, A, B> {
		static constexpr std::size_t value = BinaryCondition<N, A, B>::value;
	};
 
    template <int N, typename A, typename FIRST, typename... REST>
	struct count_binary_true<N, A, FIRST, REST...> {
		static constexpr std::size_t value = count_binary_true<N, A, FIRST>::value + count_binary_true<N, A, REST...>::value;
	};

//	--------------------------------------------------------------------------------------------------------------------------
    template <bool CONDITION>
	struct true_if : std::conditional<CONDITION, std::true_type, std::false_type>::type {};

    template <int N, typename... T>
	struct all_unary_true : true_if<count_unary_true<N, T...>::value == (sizeof...(T))> {};

    template <int N, typename... T>
	struct all_binary_true : true_if<count_binary_true<N, T...>::value == (sizeof...(T) - 1)> {};
}

template <typename... TYPES>
typename std::enable_if<Utility::all_binary_true<Utility::IsConvertible, int, TYPES...>::value>::type foo (TYPES&&...) { 
	std::cout << "All are implicitly convertible to int." << std::endl;
}

struct Thing{};

template <typename... TYPES>
typename std::enable_if<Utility::all_binary_true<Utility::IsBaseOf, const Thing&, TYPES...>::value>::type foo (TYPES&&...) {
	std::cout << "All are derived from Thing." << std::endl;
}

template <typename... TYPES> 
typename std::enable_if<Utility::all_unary_true<Utility::IsString, TYPES...>::value>::type foo (TYPES&&...) {
	std::cout << "All are strings." << std::endl;
}

template <typename... TYPES>
typename std::enable_if<Utility::all_unary_true<Utility::IsPointer, TYPES...>::value>::type foo (TYPES&&...) {
	std::cout << "All are pointers." << std::endl;
}


So the exercise I'm currently pondering over: Have foo(TYPES...) enabled only if the parameters are such that the first satisfies CONDITION_1, second satisfies CONDITION_2, the third satisfied CONDITION_3, ... and nth onwards satisfy CONDITION_N (which can be default to true). I think that's the most general it can get. Of course, this means that the above all_unary_true, all_binary_true functions won't be used. So for example,
 
foo(1, 'a', 2.5, "hi", frank, std::cout, ...);

will compile because 1 satisfies is_integral, 'a' satisifies is_convertible to int, 2.5 satisfies is_same as double, and the others satisfy !is_pointer (or simply put no condition on the others). But even if there is no solution to this, I'm quite content with the above solution already, which anybody can benefit from. I'm not even sure if my question make sense. There will be 2 template packs bool.... CONDITIONS, typename... ARGS ???
Last edited on
The way I accomplished that was by using template template parameters. Check out my tuple_template_forward metafunction:
https://github.com/LB--/resplunk/blob/server/include/resplunk/util/TMP.hpp#L31-L37
You can see I used it to eliminate duplicate types from a std::tuple; in your case you can call the provided metafunction and just check if ::value is true (obviously no tuples involved in your case).
Last edited on
Topic archived. No new replies allowed.