Home > Blockchain >  Working around limitation of `constexpr` static data member with same type as enclosing class
Working around limitation of `constexpr` static data member with same type as enclosing class

Time:12-15

I want to give constexpr capabilities to a Color class that looks like this:

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

My desire is to keep the API of this class unchanged, therefore I'd like to completely remove color.cpp and make these changes to the header file:

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

However, the code above does not compile as constexpr static data members with the same type as the enclosing class are not allowed in C .

Of course, I could change the API to something like ColorConstants::Red and move the Red object out of the class, but I do not want to break existing users.

The only workaround I've thought of looks like this:

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

The above workaround allows most existing code that uses Color to still compile after the changes, but it obviously fails whenever the Red is not used in a context where an implicit conversion to Color is required.

So, my question is: is it possible to work around the constexpr limitation seen above, turning Red into a constant expression, while still retaining the original Color::Red syntax and avoiding breaking existing code?

CodePudding user response:

The way to do this is to have the declaration be simply const, but have an out-of-line definition that is inline constexpr, like so:

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.

CodePudding user response:

I would:

  • separate the constants out into another class
  • put the preferred color representation in a class in a namespace
  • create a new class that recreates the old API in another namespace but with a using declaration or similar

It is more code than @Artyer answer but this would give you a way to migrate users away from the constant placement that is mismatched with the language like while preserving old code. Once the migration is complete you can mostly just delete code to clean-up.

Something like:

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

  • Related