5

I'd like to be able to check invariants for classes that are used in metaprograms. My first naive approach was

template <int N>
struct digit
{
  static_assert((N >= 0) && (N < 10), "bad invariant");
};

using boom = digit<99>;

However this compiles without any problems. The static assertion is triggered only when the illegal class is constructed.

It is possible when adding an additional template parameter:

#include <type_traits>

template <int N, 
          typename = typename std::enable_if<(N >= 0) && (N < 10)>::type>
struct digit;

using crash = digit<-7>;

When I wanted to apply this technique to a class that is used as a list of types:

#include <type_traits>

template <typename ...> struct are_integral;

template <typename T, typename ...Ts>
struct are_integral<T, Ts...>
{
  static const bool value = std::is_integral<T>::value &&
                            are_integral<Ts...>::value;
};

template <>
struct are_integral<> : std::true_type { };



template <typename ...Ts,
          typename = typename std::enable_if<are_integral<Ts...>::value>::type>
struct list;

using ok = list<int, long, char>;
using bad = list<double>;

It simply does not work since gcc complains that

error: parameter pack 'Ts' must be at the end of the template parameter list struct list;

Even if it would work, the class is useless as the template parameter pack does not reflect the typelist.

So I tried to use an "illegal" base class:

template <typename> struct check;

template <typename ...Ts>
struct list : check<typename std::enable_if<are_integral<Ts...>::value>::type>
{ };

using ok = list<int, long, char>;
using bad = list<double>;

This compiles without problems.

Is there any way to accomplish something like that in c++11 or do I have to wait for concepts?

dyp
  • 38,334
  • 13
  • 112
  • 177
MadScientist
  • 3,390
  • 15
  • 19
  • If you don't mind the indirection, you could use an alias template instead of directly referring to the `list` template. I wouldn't consider this a nice solution, though. – dyp Jul 03 '15 at 14:10
  • Related question I asked a while ago: http://stackoverflow.com/questions/11251569/should-static-assert-be-triggered-with-a-typedef. As you can see, the problem is related to template instantiation, which is deferred as long as possible by the compiler. At the moment, I can't think of a way to trigger instantiation without explicitely accessing one of the class member, but I'll keep thinking! – Luc Touraille Jul 03 '15 at 14:19

1 Answers1

2

Your problem arises because the template is not instantiated when it is aliased, so the static_assert does not trigger.

If this is acceptable, you could add some indirection and use a builder metafunction to create your compile-time list. This metafunction would perform the check.

template <typename ...Ts>
struct make_list
{ 
    static_assert(are_integral<Ts...>::value, "all types must be integral");
    typedef list<Ts...> type;
};

using ok = make_list<int, long, char>::type;
using bad = make_list<double>::type;

Another solution is to use a dummy type to wrap your parameter pack into a first-class type.

// dummy wrapper
template <typename ...>
struct pack;

template <typename ...> struct are_integral;

// specialization for wrapped packs
template <typename ...Ts>
struct are_integral<pack<Ts...>> : are_integral<Ts...>
{
};

template <typename T, typename ...Ts>
struct are_integral<T, Ts...>
{
  static const bool value = std::is_integral<T>::value &&
                            are_integral<Ts...>::value;
};

template <>
struct are_integral<> : std::true_type { };

// helper type which performs the check
template <typename Pack,
          typename = typename std::enable_if<are_integral<Pack>::value>::type>
struct list_helper;

// actual type (alias) you will expose
template <typename ...Ts>
using list = list_helper<pack<Ts...>>;

using ok = list<int, long, char>;
using bad = list<double>; // compiler error

Using a wrapper often comes in handy when dealing with parameter packs, because it makes them more amenable to manipulation: the wrapper is a type like any other, which can be stored, appear anywhere in the parameter list, be passed to unary metafunctions, etc.

Community
  • 1
  • 1
Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
  • While this is ok for someone using the list but still the expression `list` could be `using`d or used as template parameters without any error from the compiler. I really would like to be able to verify that no invariants are broken in my own metaprogramming-code that manipulates such classes. – MadScientist Jul 06 '15 at 08:45