10

When having this code compiling with -Warray-bounds. I get a warning when declaring array2 array index 3 is past the end of the array (which contains 3 elements). But not when declaring array1 even though it has to be the same type thus carrying the same size information. Is this a bug in clang?

enum class Format : int {
  Off = 55,
  FormatA = 66,
  FormatB = 77,
};

inline Format (&AllFormats())[3] {
  static Format values[] = {
    Format::Off,
    Format::FormatA,
    Format::FormatB
  };
  return values;
}

int main()
{
    auto array1 = AllFormats();    
    auto v3 = array1[3];

    Format (&array2)[3] = AllFormats();    
    v3 = array2[3];
}
schoetbi
  • 12,009
  • 10
  • 54
  • 72
  • 1
    How have you verified they have the same type? You might want to recheck that. – chris Jun 03 '19 at 13:56
  • Instead of C-style arrays, use `std::array`. You'll find that the code is much easier to write and much easier to understand. – Pete Becker Jun 03 '19 at 21:16
  • @pete You are right. In my code I would use std::array. The code shown was roughly generated by the google flatbuffers compiler. – schoetbi Jun 04 '19 at 03:42

4 Answers4

7

even though it has to be the same type

You’d think that. But if you check, you’ll find that they actually don’t have the same type:

std::cout << typeid(array1).name() << "\n";
std::cout << typeid(array2).name() << "\n";
P6Format
A3_6Format

Oops. The array returned by AllFormats decays to a pointer when assigned to an auto variable because that’s how the type deduction rules for auto work. Compare:

int& foo() {
    static int x = 42;
    return x;
}

auto x = foo(); // Type of `x` is `int`, not `int&`.

To prevent this, declare array1 as auto& or auto&&.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    It's stuff like this that really makes me lose faith in C++! – Lightness Races in Orbit Jun 03 '19 at 16:34
  • 1
    @LightnessRacesinOrbit The deduction rules are unintuitive but at least they’re consistent. A lot of these issues are avoided by simply never using C arrays. Personally I violate that rule but I should probably just swallow the bitter pill and use `std::array` everywhere. None of that should detract from your valid criticism though. – Konrad Rudolph Jun 03 '19 at 16:44
  • Indeed. I've started preferring `std::array` but can't promise I always bother ;) – Lightness Races in Orbit Jun 03 '19 at 16:45
4

array1 is a pointer.

Useauto&& instead of auto there.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Great this fixed it. But I need to learn more about what ``auto&&`` actually means. Thanks – schoetbi Jun 03 '19 at 14:00
  • `decltype(auto)` can also work as pointed out here: https://stackoverflow.com/questions/16949016/how-to-declare-array-with-auto – Valeriy Savchenko Jun 03 '19 at 14:01
  • @ValeriySavchenko Sure, but that is more verbose, and only makes a difference that doesn't really matter (an rvalue reference to an extended lifetime temporary, vs a value) in practice (like, 9999/10000 times). – Yakk - Adam Nevraumont Jun 03 '19 at 14:06
4

But not at line 16 even though it has to be the same type

Assuming by it you refer to auto array1 = AllFormats(), then it doesn't have the same type. auto is never deduced to be a reference, so array1 is not a reference. It is a non-reference, and is deduced to be the decayed result, i.e. a pointer to Format.

As the pointer type doesn't carry information about the size of the pointed array, the compiler wasn't able to prove that the subscript operator overflows the array.

To declare a reference, you can use either:

auto&          array1 = AllFormats(); // 1.
auto&&         array1 = AllFormats(); // 2.
decltype(auto) array1 = AllFormats(); // 3.
  1. Declares explicitly an lvalue reference.
  2. Declares a universal reference, which collapses into an lvalue reference, because AllFormats returns an lvalue reference. It would be an rvalue reference if AllFormats returned Format&&.
  3. auto type deduction uses different rules from decltype deduction. One key difference is that auto is never a reference, while decltype(E); may be a reference, depending on the expression E. decltype(auto) var = E allows a declaration using the decltype rules as if decltype(E) was used.
eerorika
  • 232,697
  • 12
  • 197
  • 326
2

In

auto array1 = AllFormats();    
auto v3 = array1[3];

array1 is not an array, so no bounds can be checked. Even though you return by reference, auto is not going to deduce one so instead the array decays into a pointer and array1 gets deduces as a Format *.

Format (&array2)[3] = AllFormats();    
v3 = array2[3];

Generates a warning because array2 is a reference to an array so it knows the size.


To get auto to deduce an array you either need to use auto&, which will only work if what is returned is an lvalue reference, or auto&& which will bind a reference to anything.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402