6

I have a templatized function and I want to static_assert that it's type has a size of three. This code illustrates what I'm trying to do, but doesn't work:

template < typename T >
void foo( T& param )
{
    // This line is the one that I need to figure out how to write
    static_assert( 3 == std::extent< T >::value, "param must have a size of 3" );
}

int main( void )
{
    int cArray[3];
    std::array< int, 3 > stdArray;

    foo( cArray );
    foo( stdArray );
}
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

4 Answers4

11

std::extent is defined for built-in arrays. For std::array use std::tuple_size instead. I don't know some trait that works on both, but it's easy to write one:

template<typename T>
struct array_size : std::extent<T> { };

template<typename T, size_t N>
struct array_size<std::array<T,N> > : std::tuple_size<std::array<T,N> > { };

and here's your foo, corrected/generalized (live example):

template < typename T >
void foo( T&& param )
{
    using U = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
    static_assert( 3 == array_size<U>::value, "param must have a size of 3" );
}

Prefer a universal reference T&& param, otherwise only lvalues can be used.

iavr
  • 7,547
  • 1
  • 18
  • 53
  • 1
    That's true, but even so, he'll continue to have the same error for cArray, which is a built-in array – DrD Mar 28 '14 at 12:51
  • @DrD Why is that? Its size is known at compile time, right? – iavr Mar 28 '14 at 12:52
  • @aschepler You're right, not after the change to T& param. The problem is that, as it is defined by the OP, *foo* receives a copy of the array. In any case, the error persist despite the T& becuase std::array is not accepted by std::extent – DrD Mar 28 '14 at 12:55
  • @DrD Well, for such type normalization you need to use `std::remove_reference` followed by `std::remove_cv`. It's not a good idea to embed these into a type trait. – iavr Mar 28 '14 at 12:57
  • `foo(T param)` would receive a pointer to the array's first element, not a copy of the array. And the OP has been edited. – aschepler Mar 28 '14 at 12:59
  • @DrD Oh, I now see what you mean. I think `T&& param` will preserve the array type, otherwise it is decayed to a pointer. I'll check and update. – iavr Mar 28 '14 at 12:59
  • @ iavr I agree it is not a good idea. But the OP wants to know why he's having *that* error. – DrD Mar 28 '14 at 12:59
  • @iavr I like your idea, but the line *using U...* gives me an error: "error C2143: syntax error : missing ';' before '='" and a few more subsequent ones derived from it. Can anybody confirm? – DrD Mar 28 '14 at 13:20
  • @DrD Don't know, can you copy the complete error message? I've checked clang/gcc, see also live example above. – iavr Mar 28 '14 at 13:54
  • That's the complete error message. I'm using VS2012 because it's the only thing I've got at hand at the moment. It could be environment dependent, who knows, that's why I asked for confirmation. Maybe the OP could contribute?? – DrD Mar 28 '14 at 14:05
  • DrD and iavr, I worked with this a little to build what I think is a cleaner solution. I have added the solution as an answer. – Jonathan Mee Mar 28 '14 at 14:10
  • @DrD, you can try using http://www.compileonline.com/compile_cpp_online.php to compile if you don't have access to Linux, I believe it uses gcc. – Jonathan Mee Mar 28 '14 at 14:52
  • @DrD I now realize: `using U = ...` is a C++11 feature, so is it supported by your compiler? – iavr Mar 28 '14 at 18:14
  • I would have assumed so, but then VS haven't implemented all the standard yet. – DrD Mar 28 '14 at 21:26
2

This builds on iavr's solution.

template < typename T >
void foo( T& param )
{
    static_assert( 3 == ( std::is_array< T >::value ? std::extent< T >::value : std::tuple_size< T >::value ), "param must have a size of 3" );
}
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • After correcting my own mistakes, it seems to work! Very elegant solution, congrats! – DrD Mar 28 '14 at 14:25
  • 1
    Nice. Yet another alternative could be to define `template using array_size = std::integral_constant::value ? std::extent< T >::value : std::tuple_size< T >::value>;`. – iavr Mar 28 '14 at 15:16
  • @iavr I see the `using` keyword in your answer too. Can you explain to me what that's doing? – Jonathan Mee Mar 28 '14 at 15:25
  • @JonathanMee See [type alias, alias template](http://en.cppreference.com/w/cpp/language/type_alias) (since C++11). The former (as in my answer) is just an alternative syntax to `typedef`. The latter (as in my previous comment) is to `typedef` (roughly) what is a class template to a plain class. It's extremely convenient if you play with types. – iavr Mar 28 '14 at 18:10
  • @iavr Wow, OK, so I could use `static_assert( 3 = array_size< T >, "param must have a size of 3" );` or could I even use this in `std::enable_if`? I believe I'm understanding type alias well enough, but I'm still trying to figure out `std::integral_constant` and alias template for that matter. Anyway, if you feel like editing this into your answer, I'd be happy to accept it. – Jonathan Mee Mar 31 '14 at 11:53
  • @JonathanMee The logical expression would have to be `3 == array_size::value` or (shorter) `3 == array_size{}`. In the latter case, the `integral_constant` object is implicitly converted to `size_t`, then compared to `int`. On the other hand, `::value` is a static integral-type member. See "possible implementation" [here](http://en.cppreference.com/w/cpp/types/integral_constant). – iavr Mar 31 '14 at 12:03
  • @JonathanMee And yes, one could also use `enable_if` instead of `static_assert`. The difference is that in this case you could have other overloads of `foo` as well. When this test fails (not array or size not equal to 3), other overloads may still be used while this one doesn't yield an error. If there's just one `foo`, then both options have the same effect. – iavr Mar 31 '14 at 12:08
1

I can't think of a way to write a single function that handles both, but this is what overloading is for.

template <std::size_t N, typename T, std::size_t Bound>
void check_array_size( T (&)[Bound] )
{
    static_assert(Bound == N, "incorrect array size");
}

template <std::size_t N, typename T, std::size_t Bound>
void check_array_size( const std::array<T,Bound>& )
{
    static_assert(Bound == N, "incorrect array size");
}

template <std::size_t N>
void check_array_size( ... )
{
    static_assert(N<0, "argument is not an array");
}

template <typename T>
void foo(T& param)
{
    check_array_size<3>(param);
    // actual function implementation...
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

I suspect you're having this error because at compile time (which is when *static_assert* is evaluated) the dimensions of cArray and stdArray are unknown, and std::extent returns zero:

http://en.cppreference.com/w/cpp/types/extent

In fact, the error disappears if you change the condition to equal to zero:

static_assert( std::extent< T >::value == 0, "param is not zero" );

Nevertheless, as it has already been pointed out in other posts, std::extent won't work for std::array, only for built in arrays such as cArray

AFTER OP's EDIT: Now that you have modified foo to accept a reference, you'll have that std::extent reports 3 for cArray and 0 (zero) for stdArray. Hence, you'll carry on having the exception at compile time, because the dimension of stdArray is not 3.

DrD
  • 419
  • 4
  • 14