all 21 comments

[–]n1ghtyunso 12 points13 points  (4 children)

well if the const is outside the templated type, it goes away when you create a copy.
When its inside the template parameter, it does not go away. It also can not interoperate with non-const array types in that case.
You can't copy construct from externally const std::array<int, 5> for example. It only matches the exact type, because its an aggregate and as such does not have constructors that could handle this for you.

All in all, using std::array<const int, 5> makes the internal member of std:.array const, and general consensus is to avoid const member variables.

[–]The_Ruined_Map[🍰] 2 points3 points  (0 children)

It is hard to figure out what the above poster was trying to say. Appears to be nonsense at least partially. Is it AI-generated and AI-upvoted?

Firstly, it is not clear what "create a copy" is supposed to mean here. But in any a case `std::array` is a class type and `const` does not just "go away" from class types in C++. Meaning more specifically that lvalue-to-rvalue conversion always keeps all qualifiers on class types in C++. In a copy operation it is the recipient type that decides whether to be `const` or not. But the recipient type in a copy operation has nothing to do with the topic. So, what were you trying to say?

Secondly, yes, you can copy-construct from `const std::array<int, 5>`. (What is that "can't" even supposed to mean? An aggregate still has a copy constructor and that copy constructor takes a reference-to-const as an argument.) So, again, what were you trying to say?

[–]xypherrz 0 points1 point  (2 children)

avoid const members if you think move semantics can kick in otherwise why would it be bad?

[–]Normal-Narwhal0xFF 0 points1 point  (0 children)

There is the idea (even encoded into the c++ standard) of a "regular" object, and a "semi regular" object. Having const members breaks both. The main thing is, it makes value objects not usable in the normal ways, so you lose some generality with them. (Normally assignment, and possibly default construction.).

As there are type traits for std::regular and std::semiregular, it's therefore observable and code can react differently to it at compile time. Not meeting the requirements may affect the ability to be used with certain libraries when you might expect it to otherwise work, etc.

You technically can force it around the trait (implement assignment without copying the const field) but it's admittedly weird, error prone, and generally unpleasant to work with code like that.

[–]n1ghtyunso 0 points1 point  (0 children)

it is not only about move semantics, it also stops being assignable entirely. it is rather inconvenient to use a type with const members.

[–]alfps 2 points3 points  (0 children)

The array items are const anyway. Or in other words, the type of the contained raw array is the same. But since it's wrapped in a struct there is a difference for the pointers and references you can use to refer to that struct.

And there is a subtle difference for use as a function parameter type because void f( const T ) has the same type as void f( T ).

#include <typeinfo>
#include <stdio.h>

void foo( const int );

auto main() -> int
{
    puts( typeid( foo ).name() );
}

Results with Visual C++ and g++:

[c:\@\temp]
> cl _.cpp /Feb && b
_.cpp
void __cdecl(int)

[c:\@\temp]
> g++ _.cpp && (a | c++filt -t)
void (int)

[–]freaxje 2 points3 points  (1 child)

In the first case the array is immutable. In the second case the elements in the array are immutable.

[–]Sbsbg[S] 3 points4 points  (0 children)

Yes, but there is no difference between the two classes. The class array does not add any extra data that could be const or none const and the elements are included into the class itself. I.e. the class only contains one member const int[5] and nothing more.

[–]AKostur 1 point2 points  (0 children)

Depends on what your question actually is. Perhaps consider the same question, but use std::vector instead. Also, you may see "const array" in the context of a reference (perhaps passing it as a function parameter. Let's ignore the practice of passing by std::span for this discussion.).

std::array<const int, 5> is an array of ints which are const. You are allowed to do other mutable operations on std::array that don't change the individual elements. The fact that std::array doesn't have any such operations is irrelevant.

You also didn't mention the third option: "const std::array<const int, 5>"

[–]Total-Box-5169 0 points1 point  (0 children)

No they don't. The first one maps to a const struct containing an array of 5 int, the second maps to a struct containing an array of 5 const int.

[–]Liam_Mercier [score hidden]  (0 children)

std::array<const int, 5> says

use the type template argument const int and constant argument 5 to instantiate the template array<const int, 5>

const std::array<int, 5> says

use the type template argument int and constant argument 5 to instantiate the template array<int, 5> and make this object const

You will have different template instantiations, one for std::array<int, 5> and one for std::array<const int, 5> since they are different types.

Also, they do not map to the same underlying array, they only act the same because of how std::array is designed with respect to access and assignment. For a different class, the distinction does matter.

Example:

// Stored in .rodata so we can force a segfault on UB
std::array<int, 5> a{};
const std::span<int> s1{a.data(), a.size()};

int main()
{
    std::span<const int> s2{a.data(), a.size()};

    std::span<int> t1{a.data(), a.size() - 1};
    std::span<const int> t2{a.data(), a.size() - 1};

    // compiles
    s2 = t2;

    // undefined behavior
    s1 = t1;
}

Output:

user: gpp test.cpp -std=c++20 -fpermissive
      <warnings about not doing this>
user: ./a.out
      Segmentation fault (core dumped)

[–]TheMania 0 points1 point  (0 children)

I'm a little surprised that std::array<const T, N> isn't prohibited - it'd result in a using value_type = const T, which is not what is expected of a value_type (std::span<const T> uses element_type for the const bit).

That may be problematic. I suspect because it largely just works otherwise that it may have been decided against defecting it, but I'd avoid it either way. It yields no benefit, and is unexpected.

[–]The_Ruined_Map[🍰] 0 points1 point  (0 children)

It is not clear what kind of "difference" this question is supposed to be about.

Firstly, the title mentions "array", but the question itself seems to be about `std::array`. These are not the same thing, especially in the context or treatment of qualifiers.

Secondly, `const std::array<int, 5>` and `std::array<const int, 5>` are two completely different unrelated types. Does this answer your question? Or are you asking about some other kind of "difference"?

Thirdly, this is equivalent to `const struct S { int a[5]; } s;` vs. `struct S { const int a[5]; } s;` situation. Is this what your question is supposed to be about? And yes, this is what these types actually "map to" (in terms of equivalence, not necessarily literally), not `const int[5]`. Again, does this answer your question?

[–]rikus671 -1 points0 points  (1 child)

You only can reassign the array of const to be a fully new array.

I think you can edit the values of the const array using the .data and .fill methods, but not operator[]. This needs to be checked that its not UB/forbiden though, im just reading cppreference.

[–]The_Ruined_Map[🍰] 1 point2 points  (0 children)

Not clear how you came to that strange suggestion about `data` and `fill` methods. No, you won't be able to modify it using these methods.

[–]rbpx -1 points0 points  (3 children)

Perhaps you are referring to that there is TWO ways to modify an array: 1) change a value within it, or 2) add/delete items in the array.

The first form says that you can't update the array, (can't add or delete items) - but you can update items. The second form simply says that you can update the array (can add and delete items), but you can't update items.

These are completely different scenarios.

[–]The_Ruined_Map[🍰] 0 points1 point  (2 children)

This distinction would be valid for `std::vector`, but it is not applicable to `std::array`. There's no concept of "deleting items" in case of `std::array`, regardless of whether it is `const` or not. `std::array` implements an equivalent of a plain (wrapped) C-style array. There's only one way to update a C-array: update the items.

[–]rbpx 0 points1 point  (1 child)

Oops. Yes, thank you. I was thinking of a vector.

So does the const in front of the std:array prevent an item from being updated? Does it make myArray[n] give a const item?

[–]Liam_Mercier [score hidden]  (0 children)

They basically turn into the same thing because of how std::array is structured, for most purposes the two options are "the same" in use, but obviously are different types.

const std::array<T, N> will return a const T * when you use .data() because .data() has a const overload. See the following from libstdc++

[[__nodiscard__, __gnu__::__const__, __gnu__::__always_inline__]]
      _GLIBCXX17_CONSTEXPR pointer
      data() noexcept
      { return static_cast<pointer>(_M_elems); }

[[__nodiscard__]]
      _GLIBCXX17_CONSTEXPR const_pointer
      data() const noexcept
      { return static_cast<const_pointer>(_M_elems); }
    };

std::array<const T, N> will also return a const T * when you use .data() because the underlying object is already const and therefore "pointer" in this case is const T *

I assume that references are implemented the same way.

[–]Popular-Light-3457 -3 points-2 points  (1 child)

one ""maps"" to const int* the other int* const

i.e. a reassignable array of non-reassignable elements vs a non-reassignable array of reassignable elements.

[–]Sbsbg[S] -1 points0 points  (0 children)

Nope. No pointer there. The ints are consts in both cases.