103

Container requirements have changed from C++03 to C++11. While C++03 had blanket requirements (e.g. copy constructibility and assignability for vector), C++11 defines fine-grained requirements on each container operation (section 23.2).

As a result, you can e.g. store a type that is copy-constructible but not assignable - such as a structure with a const member - in a vector as long as you only perform certain operations that do not require assignment (construction and push_back are such operations; insert is not).

What I'm wondering is: does this mean the standard now allows vector<const T>? I don't see any reason it shouldn't - const T, just like a structure with a const member, is a type that is copy constructible but not assignable - but I may have missed something.

(Part of what makes me think I may have missed something, is that gcc trunk crashes and burns if you try to instantiate vector<const T>, but it's fine with vector<T> where T has a const member).

HighCommander4
  • 50,428
  • 24
  • 122
  • 194

5 Answers5

74

No, I believe the allocator requirements say that T can be a "non-const, non-reference object type".

You wouldn't be able to do much with a vector of constant objects. And a const vector<T> would be almost the same anyway.


Many years later this quick-and-dirty answer still seems to be attracting comments and votes. Not always up. :-)

So to add some proper references:

For the C++03 standard, which I have on paper, Table 31 in section [lib.allocator.requirements] says:

T, U any type

Not that any type actually worked.

So, the next standard, C++11, says in a close draft in [allocator.requirements] and now Table 27:

T, U, C any non-const, non-reference object type

which is extremely close to what I originally wrote above from memory. This is also what the question was about.

However, in C++14 (draft N4296) Table 27 now says:

T, U, C any non-const object type

Possibly because a reference perhaps isn't an object type after all?

And now in C++17 (draft N4659) it is Table 30 that says:

T, U, C any cv-unqualified object type (6.9)

So not only is const ruled out, but also volatile. Probably old news anyway, and just a clarification.


Please also see Howard Hinnant's first-hand info, currently right below.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • You are right. But I found nothing stating that `vector::value_type` has to be `vector::allocator_type::value_type` (which removes constness, at least with MSVC). – Alexandre C. Aug 05 '11 at 13:04
  • Also a const vector would not allow resizing. I'm still wondering what semantics I'd like to give to `vector` and I'm beginning to think it doesn't really make sense. – Alexandre C. Aug 05 '11 at 13:05
  • 46
    Bottom line: We didn't design containers to hold const T. Though I did give it some thought. And we came *really* close to doing it by accident. To the best of my knowledge, the current sticking point is the pair of overloaded `address` member functions in the default allocator: When T is const, these two overloads have the same signature. An easy way to correct this would be to specialize `std::allocator` and remove one of the overloads. – Howard Hinnant Aug 05 '11 at 13:28
  • 1
    @Howard: Does that mean `vector>` should be allowed? – HighCommander4 Aug 05 '11 at 17:13
  • 5
    @HighCommander4: I am not positive. On libc++ I can construct a vector (even a non-empty one) with a cooperative allocator. I can't do anything else (non-const) with it. I'm not sure if that fits your definition of "works". I'm also not positive if I'm unwittingly taking advantage of an extension. To become sure, I would need to invest a lot more time into this question. I've made such an investment in time before, but that was several years ago, and lots of things have changed in the interim. If it does work, it is not by design on the committee's part. – Howard Hinnant Aug 05 '11 at 19:55
  • 1
    @Howard: I can't think of any technical obstacle to being able to do `push_back`. But if it's not allowed by design, we are better off not doing it. I was just curious. – HighCommander4 Aug 05 '11 at 20:19
  • Note: accepted answer mostly to point people to Howard's comment, which I think is the real answer – HighCommander4 Aug 07 '11 at 00:19
  • 9
    A cache is often a mutable container of immutable objects, and a sorted vector is often an alternative to a map, so I disagree that a vector of const objects is of little use. – Chris Oldwood May 17 '13 at 10:13
  • 12
    It's a shame. I was using `std::vector` exactly because it's very similar to `const std::vector`, but without the negative implications of the latter for the class that's holding it. In fact, `std::vector` is EXACTLY what I need semantically in most cases where I use `vector`. Now I have to drop `const` - along with the reliability it provides. – Violet Giraffe Jun 24 '16 at 07:21
  • 1
    Another use of a collection of immutable objects is a mutable, iterable collection of const keys to other operations. In one of my projects, the concept of "read these objects in this order" was being handled with a `std::list`, where `T` was a key type in, e.g., a `std::map`, until the standard change required the removal of `const`. – Brian A. Henning Jan 28 '19 at 19:37
  • 1) since `new const T` (`make_unique`) is allowed there is no reason why a const-friendly allocation cannot exist, `.construct` and `.allocate` must cooperate though. 2) I have tried to implement a container with const elements (https://gitlab.com/correaa/boost-multi), it some work but it is not impossible. Interestingly the *internal* pointer has still to be mutable because presumably you still want to do `uninitialized_fill(data_ptr)` (or `uninitalized_ALGO(data_ptr)` in general) in the constructor. In which case an allocator that returns a const pointer is not very ideal anyway. – alfC Oct 20 '22 at 00:46
37

Update

Under the accepted (and correct) answer I commented in 2011:

Bottom line: We didn't design containers to hold const T. Though I did give it some thought. And we came really close to doing it by accident. To the best of my knowledge, the current sticking point is the pair of overloaded address member functions in the default allocator: When T is const, these two overloads have the same signature. An easy way to correct this would be to specialize std::allocator<const T> and remove one of the overloads.

With the upcoming C++17 draft it appears to me that we have now legalized vector<const T>, and I also believe we've done it accidentally. :-)

P0174R0 removes the address overloads from std::allocator<T>. P0174R0 makes no mention of supporting std::allocator<const T> as part of its rationale.

Correction

In the comments below T.C. correctly notes that the address overloads are deprecated, not removed. My bad. The deprecated members don't show up in in 20.10.9 where the std::allocator is defined, but are instead relegated to section D.9. I neglected to scan Chapter D for this possibility when I posted this.

Thank you T.C. for the correction. I contemplated deleting this misleading answer, but perhaps it is best to leave it up with this correction so that perhaps it will keep someone else from misreading the spec in the same way I did.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 9
    That's pretty amusing! (Now we just need to be really quiet about it, and let it slip into C++17 without anyone noticing :) ) – HighCommander4 Sep 23 '16 at 15:29
  • 3
    Doesn't [the allocator requirements table](https://timsong-cpp.github.io/cppwp/allocator.requirements#tab:desc.var.def) still just ban it outright? Regardless, [P0174R2](http://wg21.link/P0174R2) (which is the revision voted in) only deprecates, not removes, `address`. – T.C. Sep 27 '16 at 21:01
  • @T.C.: You're absolutely right. Thanks for the correction. – Howard Hinnant Sep 27 '16 at 21:21
  • So c++2x will finally allow `vector` :) – M.M Sep 27 '16 at 22:27
  • 1
    The answer "Bottom line: We didn't design containers to hold const T. " assumes that the goal is that the container should hold "const T". However, one could argue that the user goal is to restrict operations on the container - so that e.g. 'back()' returns "const T&" - regardless of what the container contains. – Hans Olsson Oct 07 '16 at 12:34
15

Even though we already have very good answers on this, I decided to contribute with a more practical answer to show what can and what cannot be done.

So this doesn't work:

vector<const T> vec; 

Just read the other answers to understand why. And, as you may have guessed, this won't work either:

vector<const shared_ptr<T>> vec;

T is no longer const, but vector is holding shared_ptrs, not Ts.

On the other hand, this does work:

vector<const T *> vec;
vector<T const *> vec;  // the same as above

But in this case, const is the object being pointed to, not the pointer itself (which is what the vector stores). This would be equivalent to:

vector<shared_ptr<const T>> vec;

Which is fine.

But if we put const at the end of the expression, it now turns the pointer into a const, so the following won't compile:

vector<T * const> vec;

A bit confusing, I agree, but you get used to it.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
7

Complementing the other answers, another approach is to use:

vector<unique_ptr<const T>> vec;

If it is the case where you want to enforce that only vec has ownership of its items. Or if you want a dynamic of moving items into vec and at some point move them out.

As pointed out, pointer const semantics may be confusing, but shared_ptr and unique_ptr aren't. const unique_ptr<T> is a const pointer and unique_ptr<const T> is a const pointee as you would expect.

-2

To my best knowledge, if you want each T element in your vector is const, just use const vector instead. Because if your vector is const-qualified, only the const-qualified methods that won't modify any T element can be called.

Daniel W.
  • 938
  • 8
  • 21