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:
const value_t&
and the second const value_t
.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).