【问题标题】:Guidelines to do constexpr operator-overloading?做 constexpr 运算符重载的指南?
【发布时间】:2023-03-03 10:50:01
【问题描述】:

考虑一个简单的 int Wrapper 类,其中包含重载乘法 operator*=operator*。对于“旧式”运算符重载,可以根据operator*= 定义operator*,甚至还有像Boost.Operators 这样的库以及@DanielFrey 的现代化身df.operators,可以为您减少样板文件。

但是,对于使用新的 C++11 constexpr 进行编译时计算,这种便利就消失了。 constexpr operator* 不能调用 operator*=,因为后者修改了它的(隐式)左参数。此外,还有no overloading on constexpr,因此在现有operator* 中添加额外的constexpr operator* 会导致重载解决方案不明确。

我目前的做法是:

#include <iostream>

struct Wrap
{
    int value;    

    Wrap& operator*=(Wrap const& rhs) 
    { value *= rhs.value; return *this; }

    // need to comment this function because of overloading ambiguity with the constexpr version
    // friend Wrap operator*(Wrap const& lhs, Wrap const& rhs)
    // { return Wrap { lhs } *= rhs; }    

    friend constexpr Wrap operator*(Wrap const& lhs, Wrap const& rhs)
    { return { lhs.value * rhs.value }; }
};

constexpr Wrap factorial(int n)
{
    return n? factorial(n - 1) * Wrap { n } : Wrap { 1 };    
}

// want to be able to statically initialize these arrays
struct Hold
{
    static constexpr Wrap Int[] = { factorial(0), factorial(1), factorial(2), factorial(3) };
};

int main() 
{
    std::cout << Hold::Int[3].value << "\n"; // 6
    auto w = Wrap { 2 };
    w *= Wrap { 3 };
    std::cout << w.value << "\n"; // 6
}

Live output here。我的问题是:

  • operator*=operator* 中的乘法逻辑重复,而不是用operator*= 表示的operator*
  • 因此,Boost.Operators 不再用于减少编写许多其他算术运算符的样板

问题:这是推荐的 C++11 方式,同时具有运行时 operator*= 和混合运行时/编译时 constexpr operator*? C++14 是否将这里的任何内容更改为例如减少逻辑重复?

更新:@AndyProwl 的回答被接受为惯用语,但根据@DyP 的建议,在 C++11 中,可以减少逻辑重复,但代价是额外的任务和违反直觉的风格

    // define operator*= in terms of operator*
    Wrap& operator*=(Wrap const& rhs) 
    { *this = *this * rhs; return *this; }

【问题讨论】:

  • 如果可以是constexpr,“常规”重载有什么用? IIRC constexpr 将在非constexpr 上下文中优雅地降级为运行时执行
  • @sehe 你不能有constexpr operator*=,所以constexpr operator* 不能调用它,而是需要复制提取字段等的逻辑。
  • 啊,我开始看到你真正的问题了。 不是关于有非constexpr 重载(你不需要它们!)而是关于不能共享代码,因为*= 不能constexpr。好在我已经真诚地 +1 了 :)
  • @DyP 你的意思是*this = *this * rhs; return *this; 作为operator* 的正文?我觉得这样效率不高,但也许编译器会优化并生成原始代码?
  • (作为operator *= 的正文,我猜这是一个错字?)你包装的类型无论如何都需要一个constexpr 复制ctor,所以这个分配不应该很复杂(没有内存分配等)。这意味着move 也可能不会比copy 有很多优势,但编译器优化这个赋值应该不难,因为它不应该有副作用。

标签: c++ c++11 operator-overloading constexpr c++14


【解决方案1】:

我找不到 C++11 的惯用解决方案(尽管作为一种解决方法,DyP's suggestion 似乎对我来说是可以接受的)。

然而,在 C++14 中,constexpr does not imply const(参见 C++14 标准草案 n3690 的附件 C.3.1),您可以简单地将 operator *=operator * 定义为 constexpr,然后定义后者就前者而言,照常:

struct Wrap
{
    int value;    

    constexpr Wrap& operator *= (Wrap const& rhs) 
    { value *= rhs.value; return *this; }

    friend constexpr Wrap operator * (Wrap const& lhs, Wrap const& rhs)
    { return Wrap(lhs) *= rhs; }    
};

这是一个live example,上面的程序是在 Clang 上使用-std=c++1y 编译的 - 不幸的是,GCC 似乎还没有实现这个规则。

【讨论】:

  • 您如何看待 DyP 以分配为代价来扭转依赖关系的建议?
  • @TemplateRex:是的,我自己也想过这个问题,但我没有将其作为解决方案发布,因为它违反了标准做法。在为 C++14 准备好启用更改时恢复指南将是不合适的 IMO。但是,作为 C++11 的解决方法,它应该是可以接受的。老实说,我不认为这里的性能会是一个问题——我不是编译器专家,但我希望优化器为这些简单的函数执行大量的内联。此外,我尽量避免过早优化,并避免仅仅因为对性能的假设而拒绝设计。
  • constexpr 不再暗示 const,但我很困惑为什么它可以工作:operator*= 既是多行的,又是变异的 this,但它是在编译时被调用。在 C++14 中是否同时删除了这些限制?你有工作文件报价吗?
  • @TemplateRex:是的,一些constexpr 限制已被取消,包括函数必须是单个return 语句这一事实。不确定工作文件,但是如果您比较 C++11 和 C++14 标准草案 n3690 的第 7.1.5/3 段,您可以看到区别是什么
  • 您甚至可以在 C++1y 中以非递归方式编写 factorial -- live example
猜你喜欢
  • 2019-01-14
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 1970-01-01
  • 2019-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多