1

The book says that std::array is safer and simpler than other methods of the assignment.

Here's my code:

#include<iostream>
#include<array>
using namespace std;

int main()
{
    array<int, 5> a = {1, 2, 3, 4, 5};
    a.at(-2) = 100;
    cout << a[-2] << endl;
    return 0;
}

Why is there no warning or error in this code?

Azeem
  • 11,148
  • 4
  • 27
  • 40
  • 1
    `a.at(-2)=100;` there should be an exception since size_type is unsigned https://en.cppreference.com/w/cpp/container/array/at – fas Apr 06 '20 at 08:39
  • 1
    I get the expected exception when running this code: https://ideone.com/ROoqpm – UnholySheep Apr 06 '20 at 08:39
  • 1
    Did you actually run it or compile it? It's not supposed to throw a compiler error. – Anonymous1847 Apr 06 '20 at 08:39
  • 6
    Perhaps you're expecting a compilation error, but it will throw an exception at runtime. If your book does not explain this, you need a better book. – molbdnilo Apr 06 '20 at 08:40

3 Answers3

2

The at member function does a runtime check, and the operator[] does no check (this is intentional design).

You can do compile-time checking by using get:

#include <array>
using namespace std;

int main()
{
    array<int, 5> a = {1, 2, 3, 4, 5};
    get<1>(a) = 100;  // ok
    get<-2>(a) = 100; // error
    get<5>(a) = 100;  // error
}
M.M
  • 138,810
  • 21
  • 208
  • 365
1

std::array::at() accepts an argument of size_type which usually is defined in terms of some unsinged type e.g. std::size_t / unsigned long.

The actual argument -2 is of signed int type which is implicitly converted to size_type while passing to at() and becomes a valid size_type number; but, only a wrapped-around one. See example.

By default, you don't get a compiler warning/error for implicit conversion. You have to look for your compiler options like GCC's -Wconversion, Wsign-conversion, etc. to enable these settings. In your case on GCC, -Wsign-conversion will warn you about these conversions; and, in combination with -Werror, these warnings will become errors.

Observe the compiler output of your code with compiler flags -std=c++11 -Werror -Wsign-conversion (live):

Compiler output:

<source>: In function 'int main()':

<source>:6:10: error: unsigned conversion from 'int' to 'std::array<int, 5>::size_type' {aka 'long unsigned int'} changes value from '-2' to '18446744073709551614' [-Werror=sign-conversion]

    6 |     a.at(-2)=100;

      |          ^~

<source>:7:13: error: unsigned conversion from 'int' to 'std::array<int, 5>::size_type' {aka 'long unsigned int'} changes value from '-2' to '18446744073709551614' [-Werror=sign-conversion]

    7 |     cout<<a[-2]<<endl;

      |             ^~

cc1plus: all warnings being treated as errors

Here's another example that simulates the same thing.


And, in at(), the passed argument is validated against the array size. Here, you will get a runtime exception of type std::out_of_range as described by the documentation:

If pos is not within the range of the container, an exception of type std::out_of_range is thrown.

You may look at its implementation provided by your compiler if you're so inclined to.

And, when you use an invalid index with subscript operator [] e.g. a[-2] where -2 to size_type conversion returns into a wrapped-around value, the result will be an out-of-bounds access and will cause an Undefined Behavior.

Hope this explanation helps!

Azeem
  • 11,148
  • 4
  • 27
  • 40
0

accessing invalid std::array element with .at will cause exception handler to throw run time error regardless of the case if you compile and run it in the release or debug.

Only time you would not see an error is when you use the member access operators [] as you have done in your example in release mode.

cout<<a[-2]<<endl;

in debug mode you will only get the out of bounds error after compiling and running. In release mode, it's undefined behaviour.

This is also why using .at is recommended instead of directly accessing the elements with [] operator.

Yucel_K
  • 688
  • 9
  • 17
  • question was about compile time: why there is no warring? – Marek R Apr 06 '20 at 08:57
  • all I'm seeing is why there is no error. to have no error at all when running [] must be used instead of .at in release mode basically. that's what I tried to answer. – Yucel_K Apr 06 '20 at 09:01
  • `a[-2]` results in undefined behavior. Full stop. Undefined behavior means that the language definition doesn't tell you what the program does. That has nothing to do with compiler settings; anything that the compiler does is okay, since the behavior is undefined. And note that `.at` is not "recommended"; it's sometimes useful, and often pointless. – Pete Becker Apr 06 '20 at 14:34
  • @PeteBecker a[-2] will throw an assertion error because its an std:: array in debug mode. its caught because of how std::array does the assertion check. so how is that an undefined behaviour? I'm not going to delve into why .at is recommended. perhaps I should have clarified there is a performance catch. I see your point if we end up accessing an array element with .at within same call multiple times though. – Yucel_K Apr 06 '20 at 15:22
  • @Yucel_K -- `a[-2]` **might** check for out of bounds. It is not required to. Again: the behavior is undefined. There's nothing more you can say about it, unless your compiler **documents** what it does, and that, of course, only applies to that compiler. – Pete Becker Apr 06 '20 at 15:42