为模板化 class 重载 [] 运算符的 const 版本

Overloading const version of [] operator for a templated class

关于在 C++ 中重载 [] 运算符的另一个问题,特别是它的 const 版本。

根据cppreference page on operator overloading,当重载数组下标运算符时

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.

因此,如果 value_t 恰好是内置类型 ,则 const 变体应该看起来

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

甚至可能

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

因为 const 限定符对这样的 return 值不是很有用。


现在,我有一个模板化的 class T(以保持与引用相同的命名),它与内置数据类型和用户一起使用 both -定义的,其中一些可能很重。

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]; }
};

根据上面给出的建议,我应该使用 enable_if 和一些 type_traits 来区分模板化 class 实例化和 built-in/not 内置类型。

我必须这样做吗?此建议 是为了避免对内置类型进行潜在的不必要的取消引用,还是隐藏在它背后的其他东西应该引起注意?


备注:

Whosebug 上的现有问题:

您不需要以特殊方式处理基本类型。对于非 const 变体,总是 return value_t&,对于 const 变体,总是 const value_t&

重载通常很短,就像在您的示例中一样,因此它们无论如何都会在每个调用站点内联。在那种情况下,重载 returns 是按值还是按引用都无关紧要,在任何一种情况下都会优化间接寻址。任何至少设置为低优化级别的现代编译器都应该这样做。

没有任何其他理由以不同方式处理基本类型,我能想到。


我还要提醒您,如果您是在您的示例中实现一个容器 class,return 一个引用和 return 一个值将对用户具有不同的语义。

如果您 return 对元素的 const 引用,用户可以保留该引用并观察容器元素的变化(直到引用失效以某种特定方式发生)。如果你return一个值,是不可能观察到变化的。

如果用户能够获得参考并观察某些类型的未来变化,但对于其他类型则不能,这将令用户感到惊讶。最坏的情况是,如果他们也使用类型通用代码,他们还需要调整所有模板。

此外,即使您 return 基本类型的按值,重载 returning 按值也只会通过 const 对对象的引用来调用。在大多数情况下,用户可能拥有您的容器的非 const 实例,并且要利用这种潜在的优化,他们需要在调用运算符重载之前首先将其对象引用显式转换为 const

因此,如果优化是一个问题,我宁愿添加一个额外的成员函数,它 always returns 容器元素的副本按值并且可以使用如果潜在的取消引用被确定为性能问题,则由用户执行。调用此成员函数不会比确保调用正确的运算符重载更麻烦。

我不同意以上"advice"。考虑一下:

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);

断言是否触发?它不应该触发,因为我们只是在引用容器中的值。基本上,std::as_const(t)[i] 应该与 std::as_const(t[i]) 具有相同的效果。但是,如果您的 const 版本 return 是一个值,则不会。所以做出这样的改变从根本上改变了代码的语义。

因此,即使您知道 value_t 是基本类型,您仍然应该return const&

请注意,C++20 范围正式识别不 return 来自其 operator* 或等效函数的实际 value_type& 的范围。但即使那样,这些东西也是该范围性质的基本部分,而不是根据模板参数更改的 属性(请参阅 vector<bool> 了解为什么这是一个坏主意)。