为模板化 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 内置类型。
我必须这样做吗?此建议 仅 是为了避免对内置类型进行潜在的不必要的取消引用,还是隐藏在它背后的其他东西应该引起注意?
备注:
- 此 class 积极参与了使用内置类型和自定义类型实例化的代码的热门部分。
- 代码跨平台使用,具有多个编译器,具有不同程度的优化选项。
- 因此,我有兴趣使其既正确又可移植,并避免对性能造成任何潜在损害。
- 我无法在 C++ 标准中找到任何额外的推理,但阅读 standardeze 不是我的强项。
Whosebug 上的现有问题:
- 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 加起来让我很困惑。
- this and this说说const重载的原理;但是,与我感兴趣的细节无关。此外,第一个使用
const value_t&
,第二个使用 const value_t
.
您不需要以特殊方式处理基本类型。对于非 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>
了解为什么这是一个坏主意)。
关于在 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 内置类型。
我必须这样做吗?此建议 仅 是为了避免对内置类型进行潜在的不必要的取消引用,还是隐藏在它背后的其他东西应该引起注意?
备注:
- 此 class 积极参与了使用内置类型和自定义类型实例化的代码的热门部分。
- 代码跨平台使用,具有多个编译器,具有不同程度的优化选项。
- 因此,我有兴趣使其既正确又可移植,并避免对性能造成任何潜在损害。
- 我无法在 C++ 标准中找到任何额外的推理,但阅读 standardeze 不是我的强项。
Whosebug 上的现有问题:
- 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 加起来让我很困惑。
- this and this说说const重载的原理;但是,与我感兴趣的细节无关。此外,第一个使用
const value_t&
,第二个使用const value_t
.
您不需要以特殊方式处理基本类型。对于非 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>
了解为什么这是一个坏主意)。