【问题标题】:Working around limitation of `constexpr` static data member with same type as enclosing class解决与封闭类具有相同类型的“constexpr”静态数据成员的限制
【发布时间】:2022-01-18 04:50:10
【问题描述】:

我想将constexpr 功能赋予Color 类,如下所示:

// color.hpp
struct Color
{
    Color(int r, int g, int b, int a);
    static const Color Red;
    // ...
};



// color.cpp
Color::Color(int r, int g, int b, int a) { /* ... */ }
const Color Color::Red(255, 0, 0, 255);
// ...

我希望保持此类的 API 不变,因此我想完全删除 color.cpp 并对头文件进行以下更改:

// color.hpp
struct Color
{
    constexpr Color(int r, int g, int b, int a) { /* ... */ }
    inline static constexpr Color Red{255, 0, 0, 255};
    // ...
};

但是,上面的代码不会编译为constexpr static data members with the same type as the enclosing class are not allowed in C++

当然,我可以将 API 更改为 ColorConstants::Red 之类的东西并将 Red 对象移出类,但我不想破坏现有用户。

我想到的唯一解决方法如下:

// color.hpp
struct Color 
{
private:
    struct ColorInit 
    {
        int r, g, b, a;
        constexpr ColorInit(int r, int g, int b, int a) { /* ... */ }
        constexpr inline operator Color() const { /* ... */ }
    }

public:
    constexpr Color(int r, int g, int b, int a) { /* ... */ }
    inline static constexpr ColorInit Red{255, 0, 0, 255};
};

上述解决方法允许大多数使用Color 的现有代码在更改后仍能编译,但如果在需要隐式转换为Color 的上下文中未使用Red,它显然会失败。

所以,我的问题是:是否可以解决上面看到的constexpr 限制,将Red 转换为常量表达式,同时仍保留原始Color::Red 语法并避免破坏现有代码?

【问题讨论】:

  • 会有什么问题: static constexpr Color Red () { return {255, 0, 0, 255}; } ?
  • @engf-010:那将是 API 更改。
  • 这几乎是一个duplicate,尽管完整性要求在不同的类上。

标签: c++ static initialization c++17 constexpr


【解决方案1】:

这样做的方法是让声明简单地为const,但有一个外部定义为inline constexpr,如下所示:

struct Color
{
    constexpr Color(int r, int g, int b, int a) { /* ... */ }
    static const Color Red;
    // ...
};

inline constexpr Color Color::Red{255, 0, 0, 255};
// From this point on, Color::Red can be used in constant expressions.

【讨论】:

  • 我很好奇它是如何工作的,因为你最初没有声明它constexpr。是否没有要求如果一个变量在一个位置声明为constexpr,那么它必须在所有位置声明为constexpr
  • @NicolBolas 这仅适用于函数。对于变量,constexpr 只能显式位于定义上,而不能位于任何声明上 (eel.is/c++draft/dcl.constexpr#1.sentence-1)。当我看到 <compare> 有像 std::strong_ordering 这样的类型和相同类型的静态 constexpr 成员时,我第一次了解到这一点,这就是它的完成方式:eel.is/c++draft/cmp.partialord#lib:partial_ordering
【解决方案2】:

我愿意:

  • 将常量分离到另一个类中
  • 将首选颜色表示放在命名空间中的类中
  • 创建一个新类,在另一个命名空间中重新创建旧 API,但使用 using 声明或类似声明

它比@Artyer 的答案更多的代码,但这将为您提供一种方法来将用户从与语言不匹配的固定位置迁移出去,例如同时保留旧代码。迁移完成后,您几乎可以删除代码进行清理。

类似:

namespace v2 {
struct Color
{
    constexpr Color(int r, int g, int b, int a) : r_(r), g_(g), b_(b), a_(a) {}
    int r_, g_, b_, a_;
};

}

// todo: get rid of v1::Color use and make this a namespace 
struct ColorConstants {
       static constexpr v2::Color Red{255, 0, 0, 255};
};

inline namespace v1 {
struct Color : v2::Color, ColorConstants {
    using v2::Color::Color;
    constexpr Color(v2::Color const& base) :v2::Color(base) {}
};
}


int
main()
{
    constexpr Color light_color = Color::Red;
    constexpr Color nice_color {255,165,0,255};
}

https://godbolt.org/z/ar497eYYM

【讨论】: