11

For example if we have an std::array and we instantiate an element that is out of bound using constexpr the compiler wouldn't report error:

constexpr int EvaluateSpecialArrayIndex(int a)
{ return a * sizeof(int); }

array<int, 5> arr;

cout << arr[98] << endl; //compiles fine

cout << arr[EvaluateSpecialArrayIndex(4)] << endl; //the same as above

Can't we restrict this somehow?

AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66
  • 2
    note: the standard doesn't permit for `arr[n]` to be rejected, because UB is not caused until execution reaches that point; e.g. this code might be in a function that ends up never being called. – M.M Dec 19 '14 at 00:45
  • 2
    @MattMcNabb It may be [not as simple as that](http://stackoverflow.com/q/23153445/11683). – GSerg Dec 19 '14 at 09:21
  • 4
    @GSerg UB can time travel once it's hit, but not if it isn't hit (sounds paradoxical at first but actually isn't, as the first answer indicates). I guess you could argue that if the code path is guaranteed to hit `arr[98]` then the UB could time-travel back to the compilation stage – M.M Dec 19 '14 at 09:46

4 Answers4

16

To ensure that constexpr functions are evaluated at compile time, you must force them to be by making their result constexpr. For example:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    int i = arr[6];  // run time error
}

However:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    constexpr int i = arr[6];  // compile time error
}

Unfortunately, for this to actually work, std::array must conform to the C++14 specification, not the C++11 specification. As the C++11 specification does not mark the const overload of std::array::operator[] with constexpr.

So in C++11 you're out of luck. In C++14, you can make it work, but only if both the array and the result of calling the index operator are declared constexpr.

Clarification

The C++11 specification for array indexing reads:

                reference operator[](size_type n);
          const_reference operator[](size_type n) const;

And the C++14 specification for array indexing reads:

                reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

I.e. constexpr was added to the const overload for C++14.

Update

And the C++17 specification for array indexing reads:

constexpr       reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

The cycle is now complete. The universe can be computed at compile-time. ;-)

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 3
    The meaning of `constexpr` without `const` changed from C++11 to 14. – Yakk - Adam Nevraumont Dec 19 '14 at 01:37
  • 1
    So, the non-`const`ness of the `operator[]` in `std::array` that is `constexpr` has no impact in C++11. It was added in C++14 because of the change of meaning, and the ability to mutate data in `constexpr` expressions meaning that non-`const` `constexpr` now means it is non-`const` yet `constexpr`. – Yakk - Adam Nevraumont Dec 19 '14 at 01:43
  • 1
    @Yakk: So my clarification is an attempt to clarify that the index operators from C++11 were not decorated with `constexpr`. And `constexpr` was added only to the `const` overload in C++14. The C++14 language changes for `constexpr` did not have an impact here. – Howard Hinnant Dec 19 '14 at 02:02
  • 1
    Strange how this one was implemented, as 'constexpr' functions can't do 'static_assert' on parameters. – AnArrayOfFunctions Dec 19 '14 at 09:13
  • I also believe that this is for compile-time evaluated arrays only, am I right? – AnArrayOfFunctions Dec 19 '14 at 09:58
8

If you know the array index at compile time, you could use std::get with an index, and that will cause a compilation failure if you're out of bounds

std::array<int, 4> a{{1,2,3,4}};
std::get<4>(a); // out of bounds, fails to compile

The error I get from gcc-4.9 ends with:

error: static assertion failed: index is out of bounds
       static_assert(_Int < _Nm, "index is out of bounds");

std::get only works with constant expression indices (the index is a template argument), so with std::array it can always detect out-of-bounds at compile time.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
6

Array access on an std::array is the same for a regular C-array, it never checks to see if the index is valid, it just invokes UB if it's out of range. If you want restrictions, use std::array::at() which throws an std::out_of_range() exception for values that exceed the array bounds.

arr.at(EvaluateSpecialArrayIndex(4)); // terminate called after throwing
                                      // an instance of 'std::out_of_range'

If you want a compile-time error use std::get:

std::get<EvaluateSpecialArrayIndex(4)>(arr); // error: static_assert failed
                                             // "index is out of bounds"
David G
  • 94,763
  • 41
  • 167
  • 253
  • @MattMcNabb There's a `constexpr` version of `at()` in C++14, but maybe he's not using that standard. – David G Dec 19 '14 at 00:38
  • 1
    @Matt `std::get` is overloaded for `std::array`. That's compile-time. See Ryan's answer. –  Dec 19 '14 at 00:39
  • @MattMcNabb Yeah, you're right. `std::get` is another option though. – David G Dec 19 '14 at 00:42
  • And what if it is a 'constexpr', still the error is thrown at run-time. Very nice with using templates - but I also want to put lvalues as indexes. – AnArrayOfFunctions Dec 19 '14 at 09:08
  • From what I undestand, if `arr` is an array of static or automatic duration, `arr+x-arr` is `constexpr` (equal to `x`) if `x` is `constexpr` and `x` is no larger than the length (number of elements) of `arr`. Will it be regarded as not being `constexpr` if `x` exceeds the length of `arr`? – supercat May 11 '17 at 20:57
2

Simple answer, because it would be very costly for std::array to have separate constexpr overloads to check for this sort of thing. If you want, you can write your own wrapper around std::array that offers a compile-checked access for compile time constants. Something like:

template<typename T, size_t S>
class safeArray
{
    std::array<T, S> m_arr;
    public:
    template<size_t A>
    T& safeAt() { 
        static_assert(A < S, "Index out of bounds");
        return m_arr[A]; 
    }
    // other funcs as needed
};

Then, you can do something like:

safeArray<int, 5> arr;
cout << arr.safeAt<98>() << endl; // compile error

This can generate a lot of functions, however. Most of the time, if your design is sound, you won't ever need this type of check.

Red Alert
  • 3,786
  • 2
  • 17
  • 24
  • 3
    If you wind up in a dependent scope where you have `safeArray arr` you'd need to call it as `arr.template safeAt<98>()` fyi. That's why `std::get` is a free function – Ryan Haining Dec 19 '14 at 00:48