1

我有一堂课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().

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

4

2 回答 2

2

我不想每次都解析字符串,

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

没有一个调用在常量表达式中,所以在运行时应用(优化器可能会有所帮助):

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

使用 constexpr 构造函数,它不能有主体

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;
};
于 2021-03-26T11:16:34.773 回答
2

如果您愿意定义自己的用户定义文字,您可以让它们创建constexpr Colour实例。

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

我还建议使用std::uint8_tRGBA 值,因为它非常适合该[0,255]范围。Astd::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}
于 2021-03-26T11:49:55.253 回答