How can I test that a construct must not compile?

Hi,

Say we have a three element maximum MPL style sequence (as in the tiny type in the book C++ Template Metaprogramming). I want to place a test that fails (returns false) when a tiny sequence holding more than 3 types is passed as parameter. Right now all I have is something that the compiler fails to compile when I try to create a tiny sequence with more than 3 elements - but this test cannot be left in the source code (since it will not compile), so I am searching for a way to make this "compilation failure" into a boolean value. Sort of looking for a way to take advantage of the fact that a bigger than 3 sequence is not a valid template and cannot be instantiated.

This is what I have now which cannot stay in the program tester:

 
typedef tiny<char*, char, char, char> four;


Maybe using enable_if or void_t to disable generation of a function? Or if I could state the sequence but not have it instantiated?? Is this possible?

Any ideas greatly appreciated!!

Juan
Last edited on
1
2
3
4
5
6
7
8
9
10
11
#include <type_traits>

template < typename... T > struct type_list {} ;

template < typename... T >
constexpr typename std::enable_if< ( sizeof...(T) <= 3 ), bool >::type
is_tiny( type_list<T...> ) { return true ; }

template < typename... T >
constexpr typename std::enable_if< ( sizeof...(T) > 3 ), bool >::type
is_tiny( type_list<T...> ) { return false ; }

http://coliru.stacked-crooked.com/a/58bf4553a3358480
Ok JLBorges, you got me close enough!!

Here is my final working version:

1
2
3
4
5
6
7
template < template<typename...> typename List , typename... T >
constexpr typename std::enable_if< (sizeof...(T) <= 3), bool >::type
is_valid_tiny() { return true; }

template < template<typename...> typename List, typename... T >
constexpr typename std::enable_if< (sizeof...(T) > 3), bool >::type
is_valid_tiny() { return false; }


And the use would be:

1
2
3
// must have less than 3 parameters!!
static_assert(is_valid_tiny<tiny,int,int,int,int>() == false);
static_assert(is_valid_tiny<tiny, int, int, int>() == true);


Yet its not perfect. Here I am using ints as template parameters. Could something be written that did not specify the type of the parameters but only the number of parameters?

Yes, I am probably asking too much, but hey, what's there to loose?

Regards,
Juan Dent
Is this what you're looking for?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <type_traits>

template < typename... T > struct tiny {} ;

static constexpr int max_template_args = 3;

template <typename...>
struct is_valid_tiny {};
template <template <typename...> class Tl, typename... Args>
struct is_valid_tiny<Tl<Args...>>
{ static constexpr bool value = sizeof...(Args) <= max_template_args; };

using three = tiny<int, char, bool>;
using four  = tiny<int, char, bool, double>;

static_assert(is_valid_tiny<three>::value);
static_assert(! is_valid_tiny<four>::value);


http://coliru.stacked-crooked.com/a/126c1bdb91cf452f
Last edited on
Not exactly mbozzi but thank you for your support. Actually, my tiny is specialized for one type, two types and three types, but no definition exists for more than 3 types, so that just mentioning tiny with more than 3 type parameters generates a compile time error. See?

Maybe I am asking too much... JLBorges solution is very close and helped me get there; but as usual I am looking for a more general solution (if that is possible) where I don't have to choose the types that go into the tiny sequence.

Regards,
Juan Dent
Oh - I understand now, but I'm not sure what your goal is.

Can you provide a hypothetical usage example like you did in your previous post?
I'm guessing a bit:

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
# include <type_traits>
# include <utility>

template <typename...> struct tiny;
template <typename T1> struct tiny<T1> {};
template <typename T1, typename T2> struct tiny<T1, T2> {};
template <typename T1, typename T2, typename T3> 
struct tiny<T1, T2, T3> {};

namespace detail {
    template <typename T, std::size_t I>
    using strip_index = T;
    
    // The following uses the fact that taking the sizeof an incomplete 
    // (or ill-formed) type is ill-formed.   
    template <template <typename...> class Tl, typename...Args, 
              std::size_t = sizeof(Tl<Args...>)> 
    std::true_type repeat_inst(int);
    
    template <template <typename...> class Tl, typename...Args>
    std::false_type repeat_inst(long);
}

template<template<typename...> class Tl, std::size_t N, 
         typename Is = std::make_index_sequence<N>>
struct accepts_n_template_args;

template<template<typename...> class Tl, std::size_t N, std::size_t... Is>
struct accepts_n_template_args<Tl, N, std::index_sequence<Is...>>
    : decltype(detail::repeat_inst<Tl, detail::strip_index<int, Is>...>(0))
{};

int main() {
    static_assert(  accepts_n_template_args<tiny, 2>());
    static_assert(! accepts_n_template_args<tiny, 4>());
}

http://coliru.stacked-crooked.com/a/98477230fd82e3ac

One assumption this makes is that the types accepted by tiny are unconstrained. I think this is fundamental - if the types of the arguments are not specified, there's no way to check that any particular template instantiation would be valid.
Last edited on
Topic archived. No new replies allowed.