Search code examples
c++assertion

static check an out of bounds


I have this method, that just gets an element of a member, which is a c-style array.

constexpr T get(const int&& idx) const {
    static_assert(idx >= sizeof(array) / sizeof(T));
    return array[idx];
}

I would like to statically check for a value on the parameter that will try to recover an element on the member that is out of bounds. So, the code will refuse to compile.

I tried with static assert, but, obviously:

function parameter 'idx' with unknown value cannot be used in a constant expression

What is the idiomatic way on modern C++ of achieve this? Is possible to check it at compile time?

If not, what is the less overhead version of report an illegal access to the member? I would like to mantain the code exception free.

Edit:

// call site example

decltype(auto) a = collections::StackArray<int, 5>{1, 2, 3, 4, 5};
auto val = a.get(6);

I am providing a literal (6), so I was thinking that this value should be checked at compile time. Even greater, if I will try to obtain user input for the .get() call, code could also refuse to compile.

auto in;
cin >> in;
a.get(in)  // Wrong!

But also will limit potencial operations like looping over the array and use the .get() method, I suppose. Even tho, the subscript operator could be used instead (without no bounds checking).


Solution

  • idx is not a compile-time constant, so you cannot assert this at compile-time (which is what static_assert is for). You don't know at compile-time what values the function will be called with.

    You can use a runtime assert from <cassert> instead, however that is typically meant for debug builds only (defining the macro NDEBUG as typical in release builds will disable all assert).

    The usual approach in C++ is to throw a std::out_of_range exception:

    #include<stdexcept>
    
    //...
    
    constexpr T get(std::size_t idx) const {
        if(idx >= std::size(array)) {
            throw std::out_of_range("idx out of bounds");
        }
        return array[idx];
    }
    

    I also replaced the unsafe size construction with std::size which gives you the size of an array without any potential usage errors as are common with the sizeof construction (e.g. applying it to a pointer instead of a array).

    Note that if you replace the built-in array with std::array, you get this throwing accessor for free from its .at member function.

    Furthermore, a const rvalue reference is pointless and really confusing (there is no real use case for them). Just take the index by value: const int&& -> int.

    And furthermore the index should typically be of type std::size_t to match the natural index type of built-in arrays (and other containers). At the very least it should be unsigned, because otherwise the implicit conversions in the comparison may turn negative values into positive ones and mess up your check. (The compiler should be warning you about this.)

    In either the throwing or assert case (without NDEBUG defined) the compiler will fail to compile the program if the function is called with an out-of-bounds index in a context which requires a constant expression, which is the closest you can get to doing this check at compile-time. But then again, trying to access the array itself out-of-bounds in a context requiring a constant expression will also cause a compilation failure, so an explicit compile-time check is not needed.