颜色的 C++ constexpr 构造函数

C++ constexpr constructor for colours

我有一个 class Colour:

class Colour {
public:
  std::byte r;
  std::byte g;
  std::byte b;
  std::byte a;
};

现在如果我有一个函数

void foo(const Colour& c);

我希望能够通过传递代表颜色的字符串来调用它:

foo("red"); // become (255, 0, 0, 255)
foo("#00ff00"); // become (0, 255, 0, 255)
foo("hsl(240, 100, 50)"); // become (0, 0, 255, 255)

当然我不想每次都解析字符串,我希望编译器解析它并用颜色替换字符串。

问题是我们 constexpr 构造函数,它不能有主体,必须直接初始化 r、g、b、a 值,或者我可以有一个私有成员 colour 所以我可以初始化它像这样:

class Colour {
public:
  constexpr Colour(const std::string& str) : colour(parseString(str)) {}

private:
  InternalColor colour; // contains the r, g, b, a
};

constexpr InternalColour parseString(const std::string& str) {
  // ...
  // Parse the string and return the colour
}

但我想直接访问 r、g、b、a 值,而不是间接访问,并且我不想要像 r().

这样的成员函数

那么如何在编译时解析颜色字符串并用颜色替换它呢?是的,我可以称自己为 return 颜色的 constexpr 函数,但我的想法是直接传递一个字符串。

I don't want to parse the string everytime,

即使有 constexpr InternalColour parseString(const std::string_view& str)

None 的调用在常量表达式中,因此在运行时应用(优化器可能会有所帮助):

foo("red"); // become (255, 0, 0, 255)
foo("#00ff00"); // become (0, 255, 0, 255)
foo("hsl(240, 100, 50)"); // become (0, 0, 255, 255)

你必须做的:

constexpr Colour red{"red"}; // Parsed at compile time
foo(red);
// ...

with constexpr constructors, it can't have a body

C++11 规则真的很严格。 :/

自从 C++14 规则放宽了很多。

即使在 C++11 中,您也可以使用委托构造函数来绕过该问题。

class Colour {
public:
  constexpr Colour(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) :
      r(r), g(g), b(b), a(a) {}
  // not `explicit`, as you want implicit conversion
  constexpr Colour(const std::string_view& str) : Colour(parseString(str)) {}

  constexpr Colour(const Colour& rhs) = default;
  constexpr Colour& operator= (const Colour& rhs) = default;

public:
    static constexpr Colour parseString(const std::string_view& str)
    {
        // constexpr parsing in C++11 might be non trivial,
        // but possible (one return statement only :/ )

        if (str == "red") { return {255, 0, 0, 255}; }
        // ...
    }

public:
    std::uint8_t r;
    std::uint8_t g;
    std::uint8_t b;
    std::uint8_t a;
};

如果您愿意定义自己的 user-defined literals,您可以获得这些来创建 constexpr Colour 个实例。

"[...] 文字运算符和文字运算符模板是普通函数(和函数模板),它们可以声明为内联或 constexpr,它们可能具有内部或外部链接,它们可以是显式调用,他们的地址可以被获取,等等

我还建议使用 std::uint8_t 作为 RGBA 值,因为它与 [0,255] 范围完美匹配。 std::byte 是“只是位的集合”。

C++14 示例:

#include <cstdint>
#include <iomanip>
#include <iostream>
#include <sstream>

namespace colours {
    struct Colour {
        std::uint8_t r;
        std::uint8_t g;
        std::uint8_t b;
        std::uint8_t a;
    };

    // helper to display values
    std::ostream& operator<<(std::ostream& os, const Colour& c) {
        std::ostringstream oss;
        oss << std::hex << std::setfill('0')         << '{'
            << std::setw(2) << static_cast<int>(c.r) << ',' 
            << std::setw(2) << static_cast<int>(c.g) << ','
            << std::setw(2) << static_cast<int>(c.b) << ','
            << std::setw(2) << static_cast<int>(c.a) << '}';
        return os << oss.str();
    }

    // decode a nibble
    constexpr std::uint8_t nibble(char n) {
        if(n >= '0' && n <= '9') return n - '0';
        return n - 'a' + 10;
    }

    // decode a byte
    constexpr std::uint8_t byte(const char* b) {
        return nibble(b[0]) << 4 | nibble(b[1]);
    }

    // User-defined literals - These don't care if you start with '#' or
    // if the strings have the correct length.

    constexpr int roff = 1; // offsets in C strings
    constexpr int goff = 3;
    constexpr int boff = 5;
    constexpr int aoff = 7;

    constexpr Colour operator ""_rgb(const char* s, std::size_t) {        
        return {byte(s+roff), byte(s+goff), byte(s+boff), 0xff};
    }

    constexpr Colour operator ""_rgba(const char* s, std::size_t) {
        return {byte(s+roff), byte(s+goff), byte(s+boff), byte(s+aoff)};
    }

    // constants
    constexpr auto red   = "#ff0000"_rgb;
    constexpr auto green = "#00ff00"_rgb;
    constexpr auto blue  = "#0000ff"_rgb;
}

void foo(const colours::Colour&) {

}

int main() {
    using namespace colours;

    constexpr Colour x = "#abcdef"_rgb;
    std::cout << x << '\n';

    std::cout << "#1122337f"_rgba << '\n';

    std::cout << red << green << blue << '\n';

    foo(red);                   // become (255, 0, 0, 255)
    foo("#00ff00"_rgb);         // become (0, 255, 0, 255)
    // foo("240,100,50"_hsl);   // I don't know hsl, but you get the picture
}

输出:

{ab,cd,ef,ff}
{11,22,33,7f}
{ff,00,00,ff}{00,ff,00,ff}{00,00,ff,ff}