本文用于初步探索C++17引入的std::any的使用。

Verification Case

知识补充

c++是静态类型的语言,python是动态类型的语言。从个人经历而言,很多时候,python一个函数或一个容器可以搞定的问题,在c++中可能会写许多相似度高的代码、或泛化设计、或使用多态特性,或许性能上比较占优势,但实现复杂度相对高些。

any容器可以当做一个阉割版的动态类型的设计,应用方式如下:

  1. Put int/float/sturct... to std::any container and algorithm

  2. Get from std::any container and algorithm, identify and cast to int/float/sturct...

最简单粗暴的实现模式可能是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class any {
public:
  virtual ~any() = default;
};

template <typename T>
class concrete_any : public any {
private:
  T data;
};

若作一些内存上优化,进一步的实现模式可能是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class any {
public:
  template <typename T, template... Args>
  any(Args&&... args) {
    if (sizeof(T) <= sizeof(void*))
      data.mem = std::get<0>(args);
    else
      data.ptr = (void*)new T(args...);
  }
  
  union member_type {
    uint64_t mem,
    void* ptr
  };

private:
  member_type data;
};

当然,此篇非做源码剖视,此处是为了看清楚any这个语法糖实现动态类型的可能实现。

具体的定义方式颇为简单:

1
2
3
4
std::any i = 1;
std::any f = 1.0f;
std::any s = std::string("hello");
std::vector<std::any> v = {1, 2.0f, std::string("hello")};

使用倒是比较啰嗦,需要借助any_cast

1
2
3
4
5
6
auto iDecay = std::any_cast<int>(i);
auto fDecay = std::any_cast<float>(i);
auto sDecay = std::any_cast<std::string>(i);
auto v0Decay = std::any_cast<int>(v[0]);
auto v1Decay = std::any_cast<float>(v[1]);
auto v2Decay = std::any_cast<std::string>(v[2]);

std::any接口比较少,进一步的了解可转cppreference[1].

应用场景

这里先引用stackoverflow中的某个回答[3]

  • In Libraries - when a library type has to hold or pass anything without knowing the set of available types.
  • Parsing files - if you really cannot specify what are the supported types.
  • Message passing.
  • Bindings with a scripting language.
  • Implementing an interpreter for a scripting language
  • User Interface - controls might hold anything
  • Entities in an editor (ref)

从个人的角度,any适用的场景或许是高度抽象的类型无关对象管理模块、类型无关代理模块,通过业务层面的约定而非传统的c/c++的静态约束的方式来达成业务。

Scenario

就拿gcc9.3 -std=c++17 -O2 -o filename这种简单的命令行来设计一个CommandLine,用于做命令的注册和解析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <any>
#include <map>
#include <string>
#include <gtest/gtest.h>

class CommandLine {
public:
  // Singleton should not be copyed and moved.
  CommandLine(const CommandLine&) = delete;
  CommandLine(CommandLine&&) = delete;
  CommandLine &operator=(const CommandLine&) = delete;
  CommandLine &operator=(CommandLine&&) = delete;

  // Add command key-value
  void addCommand(const std::string& name, std::any option) {
    commands[name] = option;
  }

  // Add command exists
  void addCommand(const std::string& option) {
    commands[option] = std::any(true);
  }

  // Get option with default value
  template <typename T>
  T get(const std::string& key, T&& defRet) {
    auto it = commands.find(key);
    if (it == commands.end())
      return defRet;
    return std::any_cast<T>(it->second);
  }

  // Mainly for the boolean option
  bool any(const std::string& key) {
    auto it = commands.find(key);
    if (it == commands.end())
      return false;
    return std::any_cast<bool>(it->second);
  }

  static CommandLine& ins() {
    static CommandLine cmd;
    return cmd;
  }

private:
  CommandLine() = default;

private:
  std::map<std::string, std::any> commands;
};

// For enum case
enum class CPPVersion {
  kCPP98,
  kCPP11,
  kCPP14,
  kCPP17
};

TEST(CommandLine, test) {
  auto& cmdl = CommandLine::ins();
  // enum case
  cmdl.addCommand("std", CPPVersion::kCPP17);
  // bool case
  cmdl.addCommand("O2");
  // string case(or struct case)
  cmdl.addCommand("o", std::string("filename"));

  ASSERT_TRUE(cmdl.any("O2"));
  ASSERT_FALSE(cmdl.any("O0"));

  ASSERT_EQ(cmdl.get<CPPVersion>("std", CPPVersion::kCPP98), CPPVersion::kCPP17);
  ASSERT_EQ(cmdl.get<std::string>("o", "a.out"), "filename");
}

这个命令行的例子,CommandLine仅有一个map成员,各种option的类型和定义由各个命令自身的业务约束。无论是扩展命令类型或修改,CommandLine基本无需任何适配。尤其是非通用的option可以进一步限定在有限范围内定义和使用。

参考资料

[1] std::any. https://zh.cppreference.com/w/cpp/utility/any

[2] std::any: How, when, and why. https://devblogs.microsoft.com/cppblog/stdany-how-when-and-why/

[3] When should I use std::any. https://stackoverflow.com/questions/52715219/when-should-i-use-stdany