2

I am not sure why the following code is not allowed to exist:

int main()
{
    std::vector<const int> v;
    v.reserve(2);
    v.emplace_back(100);
    v.emplace_back(200);
}

In theory, reserve() does not construct anything, as opposite to resize(). Also, emplace_back() is constructing "in place" objects, so none of this code is writing over an already constructed constant object.

Despite that, even writing the first line, std::vector<const int> v;, is already resulting in compilation error. Why is not allowed at all to have a std::vector of constants?

nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64

2 Answers2

1

As the link provided by Bathsheba tells you, the problem is that std::vector<T> is really std::vector<T, std::allocator<T>>. Up to C++20, std::allocator<T>::address() had two overloads, one which takes T& and one which takes T const&. The problem with T=const int should be obvious. C++ has no such thing as extra-const, so both overloads are equally good.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • The problem is not the return type but the argument it takes (reference vs const-reference which collapses if T is const). The return type of a function is not considered for the signature in overload resolution, so if the two `address()` overloads would only differ in the return type, we had a problem regardless whether `T` is constant or nor. – n314159 Apr 06 '20 at 12:53
  • @n314159: Fixed. – MSalters Apr 06 '20 at 15:25
1

While the "why" is best read here there are some easy ways to get what you want:

template<class T>
class as_const {
    T t;
public:
    as_const(T& t_): t(t_) {}
    as_const(const as_const&) = default;
    as_const(as_const &&) = delete;
    as_const& operator=(const as_const&) = default;
    as_const& operator=(as_const&&) = delete;

    operator const T&() const {
       return t;
    }
    const T& operator*() const { 
    // Or just a get method, anyway it's nicer to also have an explicit getter
       return t;
    }
};

std::vector<as_const<int>> vec;
vec.reserve(2)
vec.emplace_back(100);
vec.emplace_back(200);

You even can decide "how constant" your wrapper should be and provide the move constructors (note that t is not constant per-se in as_const) if you think that is reasonable for your use-case.

Note that you cannot prohibit the reallocation of your vector in this way. If you want to do that (and don't want to use a compile-time size array since you now your size only at runtime) take a look at std::unique_ptr<T[]>. But note that in this case you first have to create a mutable variant and then reseat it in a const variant, since the underlying array gets default initialized and you cannot change anything after that.

Of course there is also the possibility of working with an allocator and disallowing a reallocation. But that has no stl-implementation. There are some implementations for this type of behavior out there (I myself gave it a try once but that is a bit of a mess) but I do not know if there is anything in boost.

n314159
  • 4,990
  • 1
  • 5
  • 20