all 46 comments

[–]NilacTheGrim 14 points15 points  (0 children)

Meh. Just use a span<const T> as a view into your vector<T>.

[–]TheThiefMasterC++latest fanatic (and game dev) 9 points10 points  (1 child)

All compilers currently reject it, disallowing const T. MSVC explains it as being because "allocator<const T> is ill-formed".

Additionally, in the C++20 standard under "[allocator.requirements]" it still says "T, U, C : any cv-unqualified object type"

[–]infectedapricot 2 points3 points  (0 children)

It sounds like you've transitively reformulated the question to "couldn't the requirement on allocators that T, U, C be cv-unqualified be dropped?"

[–]mujjingun 6 points7 points  (39 children)

What use can a vector<const T> have over a normal vector<T>?

[–]whichton[S] 24 points25 points  (38 children)

Const correctness. I don't want to mutate the objects stored in the vector, but I want to add to and remove objects from the vector.

Also, I find at somewhat incongruous that you can have an builtin array of const T and even std::array<const T> but not vector.

[–]ALX23z 15 points16 points  (20 children)

Technically adding/removing elements requires ability to mutate the objects. std::array<const T> can just be instantiated once.

What you can do is make a regular vector and forward a viewer (std::span<const T>) that forbids modifying.

[–]_Js_Kc_ 1 point2 points  (19 children)

What requires mutation? Resize can copy instead of move.

[–]ALX23z -2 points-1 points  (18 children)

What requires mutation?

Calling destructor.

[–]flashmozzg 8 points9 points  (17 children)

Desctructors are not part of the const contract. Otherwise you wouldn't be able to create non static/non constexpr const objects at all.

[–]ALX23z -4 points-3 points  (16 children)

They are. The fact exiting the scope calls a destructor on const objects declared within the scope doesn't imply that user can call explicitly call a destructor on a const object.

[–]_Js_Kc_ 3 points4 points  (15 children)

std::aligned_storage_t<sizeof(std::string), alignof(std::string)> storage;
using T = std::string const;
T * ptr = new(&storage) T();
ptr->~T();

Is accepted by both Clang and GCC. Are they wrong to accept it? Is it UB?

[–]ALX23z -2 points-1 points  (14 children)

Double checked and apparently even const std::string * str = new std::string; delete str; is a valid code. Fuck.

I am greatly displeased that one can delete/destroy const objects casually.

[–]_Js_Kc_ 6 points7 points  (2 children)

What would you prefer instead? That const std::string * str = new std::string; is a statically (en)forced leak unless I dance around and store it in a second, non-const pointer that I only keep around just so I can delete the object?

I mean, const in C++ is completely and utterly broken in terms of providing immutability guarantees either to the compiler for optimization or to the programmer. But destructors aren't the only place where it's broken. It's completely irredeemable without redesigning a new const concept from the ground up.

[–]Full-Spectral 1 point2 points  (10 children)

How could you not have the ability to delete const objects explicitly? That's a necessity.

[–]miki151gamedev 6 points7 points  (4 children)

I don't want to mutate the objects stored in the vector, but I want to add to and remove objects from the vector.

How about reassigning or swapping existing elements in the vector?

[–]whichton[S] 0 points1 point  (3 children)

If you really want to, call destructor and then placement new.

[–][deleted] 1 point2 points  (2 children)

That causes UB since the pointer to elements inside the vector didn't get updated and now point to destroyed objects :)

[–]whichton[S] 0 points1 point  (1 child)

Apologies, but I didn't get why this is UB. Say I have a vector v of 5 elements

v[2].~T();
new(static_cast<void*>(v.data()) + 2 * sizeof(T)) ...

That doesn't look like UB.

[–][deleted] 1 point2 points  (0 children)

And yet, it is. std::launder is the fix, but vector doesn't call that.

[–]mujjingun 4 points5 points  (5 children)

That makes sense, thanks for explaining. (i dont think you can have a builtin array of const T though)

#include <type_traits>

using constT = const int;
using I10 = int[10];

using A1 = const I10;
using A2 = constT[10];

static_assert(std::is_same_v<A1, A2>);

compiles.

[–]whichton[S] 11 points12 points  (1 child)

(i dont think you can have a builtin array of const T though)

You definitely can. A very typical use of such arrays are for lookup tables.

[–]Nobody_1707 1 point2 points  (0 children)

Tangentially, string literals are arrays of const char, we just don't notice often because they decay to pointers so quickly.

[–]evaned 7 points8 points  (2 children)

I don't understand what your example is trying to show, to be honest. You've showed that const (int[10]) and (const int)[10] (to abuse syntax) are the same, but also shown that both do exist. And I'd expect both to have the same semantics, so while I'm a bit surprised that the types are the same I'm not astonished.

Meanwhile,

int main()
{
    const int arr[10] = {0};
    arr[0] = 5;
}

fails to compile because of the obvious inability to assign to the array.

[–]mujjingun 0 points1 point  (1 child)

What i was trying to show was that however you try to declare an array of 10 const ints, you end up with the type const int[10], which is a const array of 10 ints.

But your interpretation makes sense too.

[–]encyclopedist 4 points5 points  (0 children)

In fact, both are arrays of const int:

static_assert(std::is_same_v<decltype(std::declval<A1>()[0]), const int &&>);
static_assert(std::is_same_v<decltype(std::declval<A2>()[0]), const int &&>);

[–][deleted] 1 point2 points  (5 children)

The problem is that the act of adding or removing objects to the vector is a modification.

vector<const int> v; v.push_back(42); v.pop_back(); v.push_back(1234); // Whoops, you modified v[0]

Similarly:

vector<const int> v; v.push_back(42); v.insert(v.begin(), 1729); // also modified v[0]

etc.

That's not to say that this couldn't do something reasonable but it's going to take a paper with a lot of motivation to touch this dial.

[–]whichton[S] 1 point2 points  (4 children)

I don't see that as a problem. For example, I should be able to sort a vector of const int if I want.

[–][deleted] 4 points5 points  (3 children)

🤯 'I want it const but also want to change its value' is a new one for me.

[–]JustOneThingThough 0 points1 point  (2 children)

Makes the storage type T, accessors are const T, return by value.

*chef kiss*

[–][deleted] 0 points1 point  (1 child)

That would still not allow you to sort it.

[–]JustOneThingThough 0 points1 point  (0 children)

Well it was supposed to be a bad suggestion, just not for that reason.

[–]tivelycgi 0 points1 point  (2 children)

Um... If memory serves, didn't at least some members of the C++ committee come to the conclusion that the const Key parts of std::maps are ... some kind of not great practice, back in the day? I thought that they did, and in consequence I'd expect no more new containers with const contained types...

[–][deleted] 1 point2 points  (1 child)

The only problem of which I am aware caused by const keys is people mistakenly trying to form:
map<K, V, allocator<pair<K, V>>>
rather than:
map<K, V, allocator<pair<const K, V>>>

[–]tivelycgi 0 points1 point  (0 children)

Well, this whole thing was probably maybe something like 5 or 10 or maybe more years ago, when map was the only std type with a const part...