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)。

当然,所有这些 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的所有实例都应该共享相同的配置并且每次传递参数来选择配置太麻烦了,那么你也可以让configchooseConfig 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。好吗?