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:
- this class actively participates in a hot part of the code instantiated both with built-in and custom types.
- code is used cross-platform with multiple compilers with varying degrees of optimization options.
- thus, I am interested in making it both correct & portable, as well as avoid any potential detriment to performance.
- I was not able to find any additional reasoning in C++ standard, but reading standardeze is not my strongest suit.
Existing questions on StackOverflow:
- C++ FAQ entry on basic rules and idioms for operator overloading recommends returning a copy using a "should better" construct without explicit justification. This "should better" is slightly different than "should" in the cppreference page and adds up to my confusion.
- this and this talk about const overloading in principle; however, not about the detail that interests me. Also, the first uses
const value_t&and the secondconst value_t.
I don't agree with the above "advice". Consider this:
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 asstd::as_const(t[i]). But it doesn't if yourconstversion returns a value. So making such a change fundamentally changes the semantics of the code.So even if you know
value_tis a fundamental type, you should still return aconst&.Note that C++20 ranges officially recognize ranges which do not return actual
value_type&s from theiroperator*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 (seevector<bool>for reasons why this is a bad idea).