2

I have a performance critical code where I query N indices. How can I check compile-time with a static_assert whether exactly N indices are given, without sacrificing performance?

#include <array>

template<int N>
void test(const std::array<int, N>& indices)
{
    // static_assert:  has three elements.
    return;
}

int main()
{
    test<3>({1, 2, 3}); // OK
    test<3>({1, 2});    // Needs to crash, because 2 < 3

    test<2>({1, 2, 3}); // Crashes, because 3 > 2
    test<2>({1, 2});    // OK

    return 0;
}
9
  • 3
    It's ugly, but it kind of works: godbolt.org/z/1S11GE . The overload is ambiguous for the second case Commented Aug 8, 2019 at 21:27
  • Why use a std::array if you always want 3 items and you don't want a partial initializer? Commented Aug 8, 2019 at 21:28
  • @MichaelChourdakis Why not? That's exactly what std::array gives. Commented Aug 8, 2019 at 21:28
  • 2
    Use variadic templates instead Commented Aug 8, 2019 at 21:31
  • 1
    Why not change test() so it accepts three arguments of type int? That means the calling syntax doesn't need the {} for the initialiser (i.e. test(1,2,3) rather than test({1,2,3})) and a compilation error if you get the number of values wrong. You can also use a variadic template if needed. Commented Aug 8, 2019 at 21:35

3 Answers 3

6

How can I check compile-time with a static_assert whether three indices are given

You can't. They array is of size 3 so it will always have 3 elements. When you do something like

test({1, 2}); 

The array initializes the first two elements with 1 and 2 and then zero-initializes the last element. This is how aggregate initialization works and you can't change it.

What you need to do is either add overloads for arrays of size 1 and 2 and delete them, or you could just change the function to have 3 parameters and then it must be called with 3 values.

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

Comments

4

Here's one way to do it:

#include <array>
#include <iostream>

template<int N, typename ...Args>
void test(Args... args)
{
    static_assert(sizeof...(args) == N);
    std::array<int, N> arr{ args... };
    for (auto&& elm : arr) {
        std::cout << elm << '\n';
    }
}

int main()
{
    test<3>(1, 2, 3); // OK
    //test<3>(1, 2);    // Crashes

    //test<2>( 1, 2, 3 ); // Crashes
    test<2>(1, 2);    // OK

    return 0;
}

Uses variadic templates instead.

Comments

2

A bit ugly, but should work - a wrapper for std::array:

class MyArray 
{
public:
    MyArray(int x, int y, int z): _array{x, y, z} 
    {};
private:
    std::array<int, 3> _array;
};

void test(const MyArray&) {
    //no need to check values here
}

You won't be able to create an object of this wrapper class with less than 3 arguments. See it online.

Of course, it won't work for general case you mentioned, but it should make it possible to differentiate certain classes.

2 Comments

You can make this work for the general case by having an N parameter like std::array, and a variadic constructor which tests that the number of parameters is N.
@Justin I don't understand those well enough to use them correctly, but fortunately anyone can provide another answer here ;)

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.