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 );
}
4
  • 1
    Try passing by reference? I don't think C arrays like to be passed by value like that. Commented Mar 28, 2014 at 12:33
  • You should explain how it "doesn't work". Do you get errors? Which ones? Commented Mar 28, 2014 at 12:34
  • 1
    I think he/she's getting "param must have a size of 3" at compile time. Commented Mar 28, 2014 at 12:38
  • Thanks Neil Kirk I did miss the & there. And DrD is correct the static assert fires. Commented Mar 28, 2014 at 12:44

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

14 Comments

That's true, but even so, he'll continue to have the same error for cArray, which is a built-in array
@DrD Why is that? Its size is known at compile time, right?
@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 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.
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.
|
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" );
}

7 Comments

After correcting my own mistakes, it seems to work! Very elegant solution, congrats!
Nice. Yet another alternative could be to define template<typename T> using array_size = std::integral_constant<size_t, std::is_array< T >::value ? std::extent< T >::value : std::tuple_size< T >::value>;.
@iavr I see the using keyword in your answer too. Can you explain to me what that's doing?
@JonathanMee See type alias, alias template (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 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.
|
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...
}

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.