颜色的 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}
我有一个 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}