2组静态常量的c ++运行时分派
c++ runtime dispatch of 2 sets of static constants
动机
CLI 应用程序尝试使用 box drawing characters。但是根据标准输出设备,这可能不合适,因此有一个 -t
选项来使用纯 ASCII 文本。
这个概念只是一个例子,这适用于我们想要在运行时从两组或更多组静态常量中 select 的任何地方。
问题是什么是合理的 c++ 技术来干净地促进这一点。
所需的用法语法
这只是指示性的,但常量 "invocation" 应该是简洁的
int main(int argc, char* argv[]) {
// using derived class style here, but anything would do
auto t = std::strcmp(argv[1], "-t") == 0 ? TxtTerminal() : BdTerminal();
// here we want the syntax to be short
std::cout << "text" << t.horiz << t.right_t << '\n';
}
已经尝试过
我已经尝试了 Base 和 Derived class 样式(如上所述),但存在问题。我不能在 TxtTerminal
中放置一组常量,而在 BdTerminal
中放置一组常量(扩展 TxtTerminal
)。
- 使用静态常量是行不通的,因为 C++ 不支持 "late static binding" ,所以它总是使用您拥有实例的任何 class 的常量。
- 使用成员变量和那些 classes 的正确实例(如上所示)不起作用(很好),因为派生 class 无法初始化基 class 直接会员....
- 也不通过其构造函数初始化列表...
- 派生的 class 必须将整个集合传递给基础 class 构造函数,然后修改已经 (BaseClass) 初始化的值。真是尴尬又啰嗦。我想通过一些
struct config { .. };
可以工作
- 类似地使用单个 class,将两组常量放在
map<key,config>
中,并使用 getter 使用初始化的构造函数检索适当的常量 bool textmode
成员状态变量。
- 也许模板化解决方案采用一些模板参数?嗯,不,因为这是一个运行时开关。
- 类似于模板的想法是某种解决方案,其中
using
别名用于 2 个独立的常量命名空间..同样的问题..编译时..不是运行时。
- 使用
variable templates
有点像 new c++20 maths constants 。同样的问题,不是运行时。
当然,所有这些 const
,即 "box drawing terminal" 的控制字符字符串每个只有一个字节长。理想情况下,这些将作为可执行文件的 .text
(linux) 段中的文字初始化 const char*
压缩在一起(std::string
似乎有点矫枉过正)。无论我们在哪里组合这些常量,我们都希望 easy concatenation
使代码可读和可维护。 const char*
字符串文字不是那么容易吗?
很多选项,none看起来很棒。我错过了一些明显的东西吗?
EDIT 为了回答下面的问题以及@walnut 最初的 ,这里有一些使用 "static non-instance" 方法的更充实的代码。
其中一些未解决的问题,见评论:
// NOTE this code compiles and runs, but doesn't quite do what we need
// see comments
#include <cstring>
#include <iostream>
struct TextMode {
static inline const auto cls = "TM cls";
static inline const auto right_t = "TM right_t";
static inline const auto left_t = "TM left_t";
};
struct BlockDrawMode {
static inline const auto cls = "BD cls";
static inline const auto right_t = "BD right_t";
static inline const auto left_t = "BD left_t";
};
struct Terminal {
Terminal(bool textmode = true) { mode = textmode ? &text_mode : &block_drawing_mode; }
// what is this type? some parent class of TextMode/BlockDrawMode?!
// then we get the initilisation loop again...
// ??????? // could be a reference initilised in cstr initilisation, but that's a detail
const TextMode* mode;
static inline const auto text_mode = TextMode{};
static inline const auto block_drawing_mode = TextMode{};
// obviously this needs to be .. ^ BlockDrawMode
// but then doesn't compile because there is no class hierarchy ...
};
int main(int argc, char* argv[]) {
Terminal t{strcmp(argv[1], "-t") == 0};
std::cout << t.mode->cls << '\n' << t.mode->right_t << '\n';
// output (-t makes no difference right now, due to the above issues)
// TM cls
// TM right_t
return 0;
}
EDIT2: 我添加了一个 ,它使用 aggregate initialisation
来避免很多不必要的继承并发症。感觉有点"dirty",但看起来很干净,做工也不错?
从评论中我了解到您正在寻找的是:
如果在需要虚拟接口或继承的行为上没有任何其他差异,那么您可以只定义一个指针成员,该成员在构造时被选择用于正确的实现:
struct Terminal {
Terminal(/* parameters */) {
chooseConfig(/* some arguments */);
}
static constexpr char config1[]{/*...*/};
static constexpr char config2[]{/*...*/};
const char* config;
void chooseConfig(/*arguments*/) {
config = /*condition*/ ? config1 : config2;
};
// use `config` everywhere
};
int main(int argc, char* argv[]) {
Terminal terminal{/* arguments */};
//...
}
如果这个class的所有实例都应该共享相同的配置并且每次传递参数来选择配置太麻烦了,那么你也可以让config
和chooseConfig
static
而不是在构造函数中调用它,而是在 main
中使用 Terminal::chooseConfig(/*arguments*/);
.
调用一次
提议"self answer":
这是一个避免了 config structs
层次结构复杂化的版本。基本上 aggregate initialization
来拯救...
这个最终版本显示了一些正确的终端控制字符串和一些使用 ref to static inline struct
的调整。加上它使用 designated initialisers
。请注意,如果需要,可以从 Base Struct Config 详细信息中进行选择性覆盖。所有的"keys"都是在编译时自动检查的,所以防止在维护时出错。
struct Mode {
const char* const esc = "";
const char* const cls = "";
const char* const bd_on = "";
const char* const bd_off = "";
const char* const underline_on = "";
const char* const black = "";
const char* const red = "";
const char* const green = "";
const char* const yellow = "";
const char* const blue = "";
const char* const magenta = "";
const char* const cyan = "";
const char* const white = "";
const char* const reset = "";
const char horiz = '-';
const char vert = '|';
const char right_t = '|';
const char left_t = '|';
const char bottom_t = '|';
const char top_t = '|';
const char intersec = '|';
};
struct Terminal {
static inline const Mode text_mode;
// warning: this is a C99 extension until C++20 comes in
// but it nicely compile checks and self documents the code
// supported by gcc4.7, clang3.0 and msvc19.21
static inline const Mode box_draw_mode{
.esc = "\x1b",
.cls = "\x1b[2J",
.bd_on = "\x1b(0",
.bd_off = "\x1b(B",
.underline_on = "\x1b[4m",
.black = "\x1b[30m",
.red = "\x1b[31m",
.green = "\x1b[32m",
.yellow = "\x1b[33m",
.blue = "\x1b[34m",
.magenta = "\x1b[35m",
.cyan = "\x1b[36m",
.white = "\x1b[37m",
.reset = "\x1b[0m",
.horiz = '\x71',
.vert = '\x78',
.right_t = '\x75',
.left_t = '\x74',
.bottom_t = '\x76',
.top_t = '\x77',
.intersec = '\x6e',
};
const Mode& m;
Terminal(bool textmode = true) : m{textmode ? text_mode : box_draw_mode} {}
};
int main(int argc, char* argv[]) {
Terminal t1{true};
Terminal t2{false};
// usage
// std::cout << t1.m.cls << '\n' << t1.m.right_t << '\n';
// std::cout << t2.m.cls << '\n' << t2.m.right_t << '\n';
return 0;
}
这是一个 godbolt(注意 clang>3 和 gcc>4.7 编译指定的初始化器已经很好了)。另请注意,godbolt 显示在 -O3
上,初始化程序得到了优化,以至于 char
值刚刚成为内联寄存器加载。在 O1 上,我们可以清楚地看到 .text
段中的字符串,并且在 Terminal
构造过程中唯一发生的事情是设置 Mode& m
的单个指针指向其中一个2 个结构:mov qword ptr [rdi], rcx
。好吗?
动机
CLI 应用程序尝试使用 box drawing characters。但是根据标准输出设备,这可能不合适,因此有一个 -t
选项来使用纯 ASCII 文本。
这个概念只是一个例子,这适用于我们想要在运行时从两组或更多组静态常量中 select 的任何地方。
问题是什么是合理的 c++ 技术来干净地促进这一点。
所需的用法语法
这只是指示性的,但常量 "invocation" 应该是简洁的
int main(int argc, char* argv[]) {
// using derived class style here, but anything would do
auto t = std::strcmp(argv[1], "-t") == 0 ? TxtTerminal() : BdTerminal();
// here we want the syntax to be short
std::cout << "text" << t.horiz << t.right_t << '\n';
}
已经尝试过
我已经尝试了 Base 和 Derived class 样式(如上所述),但存在问题。我不能在 TxtTerminal
中放置一组常量,而在 BdTerminal
中放置一组常量(扩展 TxtTerminal
)。
- 使用静态常量是行不通的,因为 C++ 不支持 "late static binding" ,所以它总是使用您拥有实例的任何 class 的常量。
- 使用成员变量和那些 classes 的正确实例(如上所示)不起作用(很好),因为派生 class 无法初始化基 class 直接会员....
- 也不通过其构造函数初始化列表...
- 派生的 class 必须将整个集合传递给基础 class 构造函数,然后修改已经 (BaseClass) 初始化的值。真是尴尬又啰嗦。我想通过一些
struct config { .. };
可以工作 - 类似地使用单个 class,将两组常量放在
map<key,config>
中,并使用 getter 使用初始化的构造函数检索适当的常量bool textmode
成员状态变量。 - 也许模板化解决方案采用一些模板参数?嗯,不,因为这是一个运行时开关。
- 类似于模板的想法是某种解决方案,其中
using
别名用于 2 个独立的常量命名空间..同样的问题..编译时..不是运行时。 - 使用
variable templates
有点像 new c++20 maths constants 。同样的问题,不是运行时。
当然,所有这些 const
,即 "box drawing terminal" 的控制字符字符串每个只有一个字节长。理想情况下,这些将作为可执行文件的 .text
(linux) 段中的文字初始化 const char*
压缩在一起(std::string
似乎有点矫枉过正)。无论我们在哪里组合这些常量,我们都希望 easy concatenation
使代码可读和可维护。 const char*
字符串文字不是那么容易吗?
很多选项,none看起来很棒。我错过了一些明显的东西吗?
EDIT 为了回答下面的问题以及@walnut 最初的
其中一些未解决的问题,见评论:
// NOTE this code compiles and runs, but doesn't quite do what we need
// see comments
#include <cstring>
#include <iostream>
struct TextMode {
static inline const auto cls = "TM cls";
static inline const auto right_t = "TM right_t";
static inline const auto left_t = "TM left_t";
};
struct BlockDrawMode {
static inline const auto cls = "BD cls";
static inline const auto right_t = "BD right_t";
static inline const auto left_t = "BD left_t";
};
struct Terminal {
Terminal(bool textmode = true) { mode = textmode ? &text_mode : &block_drawing_mode; }
// what is this type? some parent class of TextMode/BlockDrawMode?!
// then we get the initilisation loop again...
// ??????? // could be a reference initilised in cstr initilisation, but that's a detail
const TextMode* mode;
static inline const auto text_mode = TextMode{};
static inline const auto block_drawing_mode = TextMode{};
// obviously this needs to be .. ^ BlockDrawMode
// but then doesn't compile because there is no class hierarchy ...
};
int main(int argc, char* argv[]) {
Terminal t{strcmp(argv[1], "-t") == 0};
std::cout << t.mode->cls << '\n' << t.mode->right_t << '\n';
// output (-t makes no difference right now, due to the above issues)
// TM cls
// TM right_t
return 0;
}
EDIT2: 我添加了一个 aggregate initialisation
来避免很多不必要的继承并发症。感觉有点"dirty",但看起来很干净,做工也不错?
从评论中我了解到您正在寻找的是: 如果在需要虚拟接口或继承的行为上没有任何其他差异,那么您可以只定义一个指针成员,该成员在构造时被选择用于正确的实现:
struct Terminal {
Terminal(/* parameters */) {
chooseConfig(/* some arguments */);
}
static constexpr char config1[]{/*...*/};
static constexpr char config2[]{/*...*/};
const char* config;
void chooseConfig(/*arguments*/) {
config = /*condition*/ ? config1 : config2;
};
// use `config` everywhere
};
int main(int argc, char* argv[]) {
Terminal terminal{/* arguments */};
//...
}
如果这个class的所有实例都应该共享相同的配置并且每次传递参数来选择配置太麻烦了,那么你也可以让config
和chooseConfig
static
而不是在构造函数中调用它,而是在 main
中使用 Terminal::chooseConfig(/*arguments*/);
.
提议"self answer":
这是一个避免了 config structs
层次结构复杂化的版本。基本上 aggregate initialization
来拯救...
这个最终版本显示了一些正确的终端控制字符串和一些使用 ref to static inline struct
的调整。加上它使用 designated initialisers
。请注意,如果需要,可以从 Base Struct Config 详细信息中进行选择性覆盖。所有的"keys"都是在编译时自动检查的,所以防止在维护时出错。
struct Mode {
const char* const esc = "";
const char* const cls = "";
const char* const bd_on = "";
const char* const bd_off = "";
const char* const underline_on = "";
const char* const black = "";
const char* const red = "";
const char* const green = "";
const char* const yellow = "";
const char* const blue = "";
const char* const magenta = "";
const char* const cyan = "";
const char* const white = "";
const char* const reset = "";
const char horiz = '-';
const char vert = '|';
const char right_t = '|';
const char left_t = '|';
const char bottom_t = '|';
const char top_t = '|';
const char intersec = '|';
};
struct Terminal {
static inline const Mode text_mode;
// warning: this is a C99 extension until C++20 comes in
// but it nicely compile checks and self documents the code
// supported by gcc4.7, clang3.0 and msvc19.21
static inline const Mode box_draw_mode{
.esc = "\x1b",
.cls = "\x1b[2J",
.bd_on = "\x1b(0",
.bd_off = "\x1b(B",
.underline_on = "\x1b[4m",
.black = "\x1b[30m",
.red = "\x1b[31m",
.green = "\x1b[32m",
.yellow = "\x1b[33m",
.blue = "\x1b[34m",
.magenta = "\x1b[35m",
.cyan = "\x1b[36m",
.white = "\x1b[37m",
.reset = "\x1b[0m",
.horiz = '\x71',
.vert = '\x78',
.right_t = '\x75',
.left_t = '\x74',
.bottom_t = '\x76',
.top_t = '\x77',
.intersec = '\x6e',
};
const Mode& m;
Terminal(bool textmode = true) : m{textmode ? text_mode : box_draw_mode} {}
};
int main(int argc, char* argv[]) {
Terminal t1{true};
Terminal t2{false};
// usage
// std::cout << t1.m.cls << '\n' << t1.m.right_t << '\n';
// std::cout << t2.m.cls << '\n' << t2.m.right_t << '\n';
return 0;
}
这是一个 godbolt(注意 clang>3 和 gcc>4.7 编译指定的初始化器已经很好了)。另请注意,godbolt 显示在 -O3
上,初始化程序得到了优化,以至于 char
值刚刚成为内联寄存器加载。在 O1 上,我们可以清楚地看到 .text
段中的字符串,并且在 Terminal
构造过程中唯一发生的事情是设置 Mode& m
的单个指针指向其中一个2 个结构:mov qword ptr [rdi], rcx
。好吗?