c++operator-overloadingconstantsc++17built-in-types

Overloading const version of [] operator for a templated class


Yet another question on overloading an [] operator in C++, in particular, its const version.

According to cppreference page on operator overloading, when overloading an array subscript operator

struct T
{
          value_t& operator[](std::size_t idx)       { return mVector[idx]; }
    const value_t& operator[](std::size_t idx) const { return mVector[idx]; }
};

If the value type is known to be a built-in type, the const variant should return by value.

So, if value_t happens to be a built-in type, the const variant should look

    const value_t operator[](std::size_t idx) const { return mVector[idx]; }

or probably even

   value_t operator[](std::size_t idx) const { return mVector[idx]; }

since the const qualifier is not very useful on such a return value.


Now, I have a templated class T (to keep the same naming as with the reference), which is used both with built-in data types and user-defined ones, some of which might be heavy.

template<class VT>
struct T
{
          VT& operator[](std::size_t idx)       { return mVector[idx]; }
    const VT& operator[](std::size_t idx) const { return mVector[idx]; }
};

According to the given advice above, I should use enable_if with some type_traits to distinguish between templated class instantiations with built-in/not built-in types.

Do I have to do it? Is this recommendation only to avoid potential unnecessary dereferencing for built-in types or something else is hiding behind it that one should be aware of?


Notes:

Existing questions on StackOverflow:


Solution

  • I don't agree with the above "advice". Consider this:

    T t = /*Initialize `t`*/;
    const T::value_t &vr = std::as_const(t)[0];
    const auto test = vr; //Copy the value
    t[0] = /*some value other than the original one.*/
    assert(test != vr);
    

    Does the assert trigger? It shouldn't trigger, because we are just referencing the value in the container. Basically, std::as_const(t)[i] should have the same effect as std::as_const(t[i]). But it doesn't if your const version returns a value. So making such a change fundamentally changes the semantics of the code.

    So even if you know value_t is a fundamental type, you should still return a const&.

    Note that C++20 ranges officially recognize ranges which do not return actual value_type&s from their operator* or equivalent functions. But even then, such things are a fundamental part of the nature of that range, rather than being a property that changes based on the template parameter (see vector<bool> for reasons why this is a bad idea).