关联式的std::tuple容器

4

有没有可能(简单地,可能重新使用std容器)定义一个“关联std::tuple”或说成“可变参数std::map”?

类似这样(此接口仅用于说明,其他可能的接口也是可以的):

AssociativeTuple<std::string> at;    // std:string is the key type
at.insert<float>("my_float", 3.14);  // 1.
at.insert<int>("my_int", 42);
at.insert<bool>("my_bool", true);
at.insert<int>("xyz", 0);
at.insert<std::string>("my_string", "hello world!");

assert(get(at, "my_float") == 3.14);  // 2.
assert(get(at, "my_int") == 42);
assert(at["my_string"] == "hello world!");  // 3.

assert(std::is_same<at.type_of("my_float")::type, float>)  // 4.

for (auto it : at) { std::cout << it.first << " = " << it.second; }  // 5.

其他期望的限制条件:

  1. 值/键集仅在运行时已知。但在编译时,用户知道(值的类型)和键之间的关系。例如,用户在编译时知道"my_float"将是一个float。换句话说,可能的键集是固定的,并且与键对应的值的类型在编译时已知。在编译时不知道的是“是否”将在容器中插入一个键。当然,映射值的值在编译时未知。
  2. 访问性能,get 应该很快
  3. 用户不必记住与键相关联的类型

我的真正问题只涉及float/int/bool类型的值(我正在将所有内容存储在std::map<std::string, float>中,并在必要时转换为int),但通用解决方案是可取的。在我的实际情况中,键始终是std::string


5
一个 std::map<std::string, variant> 是否能解决你的问题? - JBL
@JBL: 第4行应该怎么写? - Ruggero Turra
我们在谈论多少个不同的键? - MikeMB
你的修改是否与第三点相矛盾了? - MikeMB
好的,我明白了。另一个问题是关于我们是否在讨论可能是10个键还是50个以上的问题。 - MikeMB
显示剩余4条评论
3个回答

3
您可能需要一个带有多态值的地图。对于多态值,您可以使用boost::any或更好的boost::variant<>。例如:
typedef boost::variant<int, double, std::string> MyVariant;
typedef std::map<std::string, MyVariant> MyPolymorphicMap;

“用户不必记住与键关联的类型”这一点使得使用any版本不可能,对吧? - JBL
@JBL 我不这么认为。你可以从 boost::any 中获取类型信息,只是类型信息本身并没有太大的用处。实际上,boost::variant<> 更加有用。 - Maxim Egorushkin
你正在声明 MyVariant 为一个带有三个模板参数的 boost::variant。为什么只有3个而不是无限多个?我已经更好地重写了在编译时已知的内容。 - Ruggero Turra
@RuggeroTurra 在你最初的问题中,你只有三种类型的值:intdoublestd::string。你应该可以更改定义以包括任何你需要的类型。(显然,我无法在这里列出一个无限的列表,因为在这个网站上没有无限的存储空间)。 - Maxim Egorushkin

1

这个怎么样?(注意:值不是元组;每个键只有一个值。)

template<class K> using AnyMap = std::map<K, boost::any>;

AnyMap map;
map["test1"] = 124;
map["test2"] = std::string{ "some text" };

auto value = boost::any_cast<int>(map["test1"]);

能否满足“3.用户不必记住与键关联的类型”的要求?我指的是boost:any_cast<int> - Ruggero Turra
是的和不是的;boost::any有一个存储类型的访问器,但它返回一个std::typeid(相当有限)。由于这个原因,boost::variant更加灵活(您可以在variant上执行比any更多的操作)。 - utnapistim

0

是的,这是可能的,使用变量(有很多实现)或者一般来说某种包装器,可以基于联合(在您的情况下首选方式)或派生自一个公共基类。

对于像get(at, "my_int") == 42这样的事情,您将不得不重载等式运算符bool operator==(Variant&, int),但这也不应该是问题。

您不能做像std::is_same<at.type_of("my_float")::type, float>这样的事情,因为is_same是编译时表达式,但类型(您正在寻找的)仅在运行时已知。然而,您仍然可以定义一个运行时函数来执行该检查。

然而:如果在您的特定情况下,您只需要intfloat,并且您没有内存压力(使用字符串作为键似乎表明了这一点),那么我会使用double,因为它可以表示您可能想要存储的任何数字。

另一种可能性是使用两个单独的数据结构(一个用于int,一个用于float)。如果必要,您还可以在它们周围构建一个包装器,以使它们看起来像一个单一的数据结构。如果您的程序逻辑在执行查找之前总是知道与键相关联的类型,则使用多个数据结构可能特别有意义。

请不要建议使用double来代替存储所有类型的数字。double(和float)以浮点数形式存储,大多数计算结果会导致精度损失。另外一件事:您不需要重载等号运算符来比较get(variant_type, key)的结果与数字。 - utnapistim
@utnapistim:为什么不是重载?另外,由于double具有约15个小数位的精度,而'int'只有约10个小数位,因此每个整数和每个浮点数都可以由double精确表示。 - MikeMB
放弃“精确”这个词,我的意思是“不失准确性”。 - MikeMB
64位编译怎么样(64位整数不能表示为双精度浮点数 - 当您使用某些整数类型(如std::size_t)时,这会变得棘手)?关于重载,get函数将返回该值,因此最终,您将比较两个整数(不需要使用Variant&进行重载)。 - utnapistim
@utnapistim:我说的是int而不是std::size_t。虽然标准没有保证,但在我所知道的几乎所有“相关”平台上(特别是x64),sizeof(int)为4。当一个通用的get函数在编译时只知道它将访问一个变量时,它如何返回一个int或double?据我所知,这只有在你将get作为模板函数并直接指定类型,例如get<init>()时才能起作用,这也不是要求的内容。 - MikeMB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接