Iterate through TYPES in parameter pack

Hello.

My goal is to create a template class forbidden_types that gives me compilation error if I'm trying to instantiate it with one of the "forbidden types"

Example:

1
2
3
4
5
forbidden_types<int,double,float> var = "hello";  // OK

forbidden_types<int,double,float> var = 5; // Can't -> 5 is int

forbidden_types<double,float> var = 5; // OK (narrowing conversion disabled) 


I tried to do this with a Parameter Pack

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
bool forbidden_found;

template<typename FORBIDDEN, typename ACTUAL>
struct check_type
{
    check_type()
    {
	forbidden_found = is_same<FORBIDDEN, ACTUAL>::value;
    } 
};

template<typename... FORBIDDEN_TYPES>
class forbidden_types
{
public:

	template<typename T>
	forbidden_types(const T& elem)
	{
	    (check_type<FORBIDDEN_TYPES, T>, ...);
          
            if(forbidden_found)
                 // throw runtime exception             

	}
};


The main problem is that it doesn't compile

(everything refers to line 20)

syntax error: missing ')' before ','
syntax error: ','
syntax error: ')'
'FORBIDDEN_TYPES': parameter pack must be expanded in this context


I couldn't find another way to iterate through the types given at instantiation time (int, double, float) and compare each one of these with the parameter type (T)

Also, this code will throw an exception if a forbidden type is used: it should be at compile time!

Any tips?
Last edited on
In C++17, a fold expression of std::is_same_v<T, Forbidden> over logical OR would be the preferred solution:
static_assert(! (std::is_same_v<T, Forbidden> || ...));

Before that, here's one way to write it:
1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T>
constexpr bool contains() { return false; }
template <typename T, typename First, typename... Rest>
constexpr bool contains()
{ return std::is_same<T, First>::value || contains<T, Rest...>(); }

template <typename... Forbidden>
struct forbidden_types {
  template <typename T>
  forbidden_types(T const&) {
    static_assert(! contains<T, Forbidden...>(), "...");
  }
};

http://coliru.stacked-crooked.com/a/c5996ace1ae6316b
Last edited on
Thank you very much!

The thing is... I tried to implement it and I wasn't successful.

I read your code and I perfectly understand how it works... but I could've never come up with that solution.

I'd like to find solutions by myself, but template metaprogramming is a bit confusing in some cases...

I think I just need more practise...
Template metaprogramming is easy (and very natural) if we adopt the mindset of a lisp programmer: primarily, immutability of data and recursion instead of iteration. The only real extra step we need to take is: specialisation instead of selection statements (instead of if, case etc.)

Perhaps this example would help:
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
84
85
#include <iostream>
#include <vector>
#include <cassert>
#include <type_traits>

namespace run_time
{
    using type = int ;
    using type_list = std::vector<type> ;

    type_list tail( type_list types )
    {
        if( types.empty() ) return types ;
        else return { types.begin()+1, types.end() } ;
    }

    bool contains( type T, type_list types )
    {
        if( types.empty() ) return false ;
        else if( types.front() == T ) return true ;
        else return contains( T, tail(types) ) ;
    }
}

namespace compile_time
{
    template < typename... > struct type_list {} ; // vector<type>

    /////////////////////////////////////////////////////////////////////////

    // type_list tail( type_list types )
    template < typename...  > struct tail ;

    // if( types.empty() ) return types ;
    template <> struct tail< type_list<> > { using type =  type_list<> ; };

    // else return { types.begin()+1, types.end() } ;
    template < typename FIRST, typename... REST >
    struct tail< type_list<FIRST,REST...> > { using type =  type_list<REST...> ; };

    ////////////////////////////////////////////////////////////////////////////


    /////////////////////////////////////////////////////////////////////////

    // bool contains( type T, type_list types )
    template < typename T, typename...  > struct contains ;

    // if( types.empty() ) return false ;
    template < typename T > struct contains< T, type_list<> > : std::false_type {} ;

    // else if( types.front() == T ) return true ;
    template < typename T, typename... REST >
    struct contains< T, type_list<T,REST...> > : std::true_type {} ;

    // else return contains( T, tail(types) ) ;
    template < typename T, typename NOT_T, typename... REST >
    struct contains< T, type_list<NOT_T,REST...> > : contains< T, type_list<REST...> > {};

    /////////////////////////////////////////////////////////////////////////


    /////////////////////////////////////////////////////////////////////////
    template < typename T, typename U > struct equals : std::false_type {} ;
    template < typename T > struct equals<T,T> : std::true_type {} ;
}

int main()
{
    {
        using namespace run_time ;
        
        std::vector<int> seq{ 1, 2, 4, 8 } ;
        assert( contains( 4, seq ) ) ;
        assert( ( tail(seq) == std::vector<int>{ 2, 4, 8 } ) ) ;
    }

    {
        using namespace compile_time ;
        
        using seq = type_list< char, short, int, double> ;
        static_assert( contains< int, seq >::value, "" ) ;
        static_assert( equals< tail<seq>::type, type_list< short, int, double > >::value, "" ) ;
    }
}

http://coliru.stacked-crooked.com/a/b21ac56987633bbf
Thank you very much, I hope I'll become faster in writing algorithms with metaprogramming ;)
One tip that I find enormously helpful suggests designing meta-programs from the bottom-up. It relies on the observation that a meta-program is just a translator: a code generator, taking code as input and substituting it with (or transforming it into) new code as output. This means that meta-programs (macros, in general) are most often useful for eliminating structural duplication, designing domain-specific languages, and transforming code into a more efficient form*.

In my opinion, the most difficult part is identifying the specific translation which needs to occur. Once it's clear what needs to happen, the synthesis is often straight-forward. There's evidence of this above.

When you get the idea "a meta-program could help here", you can do something like the following:
1. design or write your code as if there weren't any meta-programming facilities in the language. The point of this is to concretely identify the problem your macro is supposed to eliminate, while identifying what your macro could potentially transform its input into.

2. When you decide there's a need for a meta-program, write down what an ideal invocation would look like. The point of this is to identify the ideal starting point of the transformation. Phrased another way, write down what you wish you could write.

The final step is to refine your ideal invocation and the possible result until the transformation from invocation->result becomes conceivable. Then you have to synthesize that transformation. With the work from the previous two steps to help you isolate the problem, it shouldn't be fundamentally different from any other functional programming exercise. Even though the mechanics of TMP are awkward, usually the task itself becomes straight-forward enough that it can be attacked, or at least specific enough to be researched.

In my opinion, the first two steps help to separate the meta-level from the base-level, which is helpful. They also usually prevent wasted time attempting things which are fundamentally impossible (which usually happens when conflating run-time and expansion-time).

*Boost.Spirit as a whole is a great example of this, using meta-programs to an extent which usually only appears in Lisp code.
http://www.boost.org/doc/libs/1_65_0/libs/spirit/doc/html/index.html
Topic archived. No new replies allowed.