模板化的数学函数应该采用值还是 const 引用?

Posted

技术标签:

【中文标题】模板化的数学函数应该采用值还是 const 引用?【英文标题】:Should templated math functions take values or const references? 【发布时间】:2016-10-12 21:24:34 【问题描述】:

假设我想实现一些简单的数学函数;例如,假设它是 (C++17's) std::clamp 的重新实现:此函数接受一个数字、一个下限和一个上限,如果它超出了它们定义的范围,则将该数字设置为这些边界之一。如果是具体的数字类型,比如int,我会写:

constexpr int clamp(int x, int lower_bound, int upper_bound)

    return x < lower_bound ? lower_bound : ( upper_bound < x ? upper_bound : x );

但如果它是一个模板,我看到sample implementation 这可能是标准将使用的const&amp; 而不是值。因此,让引用变得更简单,例如:

template <typename T>
constexpr T clip(const T& x, const T& lower_bound, const T& upper_bound)

    return x < lower_bound ? lower_bound : ( upper_bound < x ? upper_bound : x );

我的问题是:

对于 T 的简单数字类型,采用 const 引用有什么好处? 同上,对于将单个数字包装为数据成员的一些抽象事物的类型(例如 std::chrono 持续时间)? 为什么在任何相对简单、(constexpr?)、无副作用的数学函数的一般情况下,取一个const&amp; 是一个更好的主意(而且完全是这样)?

注意事项:

我意识到,当您拥有某种 k 维向量类型或 boost::rationals 和其他类似数字的类型时,使用 const&amp; 可能会开始有意义;但即便如此,编译器不会优化复制吗? 我不是询问任何任意 C++ 函数以及它是否应该只按值获取其参数,这显然是个坏主意。

【问题讨论】:

它们不能是简单的数字类型。它们可能是表现得像数字的类型。毕竟你可能不想复制它们。此外,一个 const 引用绑定到一个临时的,有时它很有用。 @Zereges:再说一遍,为什么?编译器不会优化差异吗? @einpoklum 它当然可以与您给定函数的任何其他类型一样 @Zereges:我猜的不是堆上有数据的类型。 【参考方案1】:
对于 T 的简单数字类型,采用 const 引用有什么好处?

没有。

同上,对于将单个数字包装为数据成员的一些抽象事物的类型(例如 std::chrono 持续时间)?

没有。

为什么在任何相对简单的(constexpr?)无副作用的数学函数的一般情况下,采用const&amp; 是一个更好的主意(而且完全是这样)?

想象一个使用动态分配的 bigint 类型;复制这样的类型很昂贵。

编译器不会优化复制吗?

只有在能够证明复制值没有副作用的情况下,这很难做到,除非所涉及的所有代码对编译器都是可见的。 (因此,如果您的 bigint 使用 GMP,那么您就不走运了。)

【讨论】:

【参考方案2】:

对于T 的简单数字类型,采用 const 引用有什么好处?

不,我不这么认为,但也没有处罚。

同上,对于将单个数字包装为数据成员的一些抽象事物的类型(例如std::chrono duration)?

同上。

为什么在一般情况下取一个 const&amp; 然后取一个值是更好的主意?

标准库算法不仅为基本类型设计,还为用户定义类型设计,复制起来可能并不便宜。对于这些类型,使用 const&amp; 可以避免复制的惩罚,同时不会损害基本类型的使用。

【讨论】:

但是,通过微不足道的编译器优化真的有惩罚吗?请参阅我的第一个注释。 我在你写评论的时候更新了答案。

以上是关于模板化的数学函数应该采用值还是 const 引用?的主要内容,如果未能解决你的问题,请参考以下文章

我应该通过 const 引用传递一个 lambda。

模板中右值引用和 const 左值引用之间的重载

C++,在函数中采用 const lvalue 和 rvalue 引用

为 const 引用和 rvalue 引用编写重载

模板类 char*,函数 const char* 的返回类型?

是否有任何理由使用右值引用重载运算符?