【问题标题】:Using templates as keys in a std::map使用模板作为 std::map 中的键
【发布时间】:2016-10-23 05:47:15
【问题描述】:

我想知道是否可以使用模板作为地图的键。例如:

std::map< <T> , Node> nodes;

本质上,我想要做的是能够拥有一堆包含任意类型数据的节点,并由该数据键入。我想我可以通过将所述数据转换为二进制并通过它键入来做到这一点,但这很麻烦,我想避免它。

为了澄清,我希望能够使用任何类型的变量作为键。例如,如果我有 2 个节点,一个包含一个 int 作为其数据,另一个包含一个 Foo 作为其数据,我希望能够使用它们的数据作为键将它们放在同一个映射中。也许地图不是我想要的,我不确定……

想法? 谢谢!

【问题讨论】:

  • 您需要选择特定类型才能实例化该类。但是,您可以查看 Boost Any:boost.org/doc/libs/1_61_0/doc/html/any.html。
  • 不清楚你要做什么
  • 运行时还是编译时?如果是编译时,您不是在寻找 template constexpr ...吗?如果是运行时,输入如下。
  • 虽然对于大量可能的类型来说并不漂亮,但Boost.Variant 是一种选择。
  • 还要考虑您是否真的需要std::map 或者std::unordered_map 是否足够,这将避免必须为不同类型定义小于比较。

标签: c++ templates dictionary


【解决方案1】:

我认为为此目的使用std::type_info 更容易:

std::map<std::type_info, std::string> m;
m[typeid(int)] = "integer";

但这真的取决于你想要达到的目标,我还不清楚。希望有帮助

【讨论】:

  • 如果不清楚问题是如何回答?
  • @HolyBlackCat:它确实用我的 gcc 5 编译,也许你使用的是旧编译器?文档还明确声明使用 std::type_info, here
  • type_index 是使用类型作为键,还是使用任何类型的键?我希望能够使用任何类型的数据作为键,而不是类型本身。
【解决方案2】:

至少如果我明白你想要什么,简短的回答是否定的。

映射中的键必须是有序的——也就是说,您必须为任何一对键 A 和 B 定义一个有序关系,其中 A 小于 B,或者 B 小于 A,或者这两个键是等价的。

给定两个完全任意类型的键,不会有明确的比较它们的方法。因此,您不能将它们用作地图的键。

要获得某种接近,您需要定义一些您想要支持的特定类型集。然后,您可以定义(大致)您想要支持的所有类型的联合。不过,仅此还不够——您还必须定义排序。根据您要完成的任务,您可能(例如)在每个对象中都有一个 ID,并按 ID 对它们进行排序。或者,您可以定义对象之间的排序,因此(例如)每个 Person 排序在每个 Dog 之前,它在每个 Dream 之前排序,等等。然后你必须像往常一样在每种类型中定义一个排序。

不过,我要警告的是,这通常涉及相当多的额外工作,并且提供的回报很少。我会说超过 90% 的时间我看到人们(试图)这样做,这是一个错误,充其量是效果不佳。如果可能的话,我会尝试寻找其他方法来解决您要解决的任何问题。

【讨论】:

  • 我认为有可能,例如,通过二进制值比较数据,但我不确定我是否可以这样做..
  • @vtlevs:不,没有意义。举个例子,它不能正确处理浮点值——它们有一个负零和一个正零,应该相互比较,但是如果你看一下这些位,它们会有所不同。跨度>
【解决方案3】:

如果您没有明确禁用 RTTI,请参阅 nogard 的回答。对于跨 DLL 的整个程序,标准类型 id 保证是唯一的。函数的地址不是这样。

我通常做的是这样的:

template<typename T>
void type_id(){}

using type_id_t = void(*)();

然后,我这样使用它:

std::map<type_id_t, Node> nodes;

nodes[type_id<AType>] = Node{...};
nodes[type_id<BType>] = Node{...};

当然,这可以通过 C++14 中的变量模板来增强。


对不起,我刚刚重新阅读了这个问题,我理解得更好了。

你想要的是std::any,但它只是 C++17。您可以改用boost::any。

看起来像这样:

std::map<std::any, Node> nodes;

nodes.emplace("string as key", Node{});
nodes.emplace(23, Node{});

只要地图可以以某种方式对std::any 的实例进行排序,它就应该可以工作。如果没有,您可以改用哈希映射:

std::unordered_map<std::any, Node> nodes;

只要地图可以散列任何值,它就可以工作。

【讨论】:

  • @vtleavs 一种能够保存任何类型值的类型。 Documentation
  • 我创建了标准库文档的链接。如果需要,我可以在此处再次粘贴:std::any
  • @RichardHodges 这就是我提到 unodered_map 的原因。散列anys 是可能的。
  • @GuillaumeRacicot 你将如何测试相等性?我已经发布了一个适用于所有地图和集合类型的答案。
【解决方案4】:

您可以创建一个类似于poly_key 的类,它可以接受任何类型,只要它是:

  • 可复制或可移动(此演示可复制)

  • 等式可比(如果用于无序地图)

  • 低于可比性(如果在地图中使用)

  • 可散列(如果在有序映射中使用)

  • ostreamable(用于此演示)

像这样:

#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <map>
#include <typeinfo>

/// a non-polymorphic container for a polymorphic key type
struct poly_key
{
    /// concept defines the key's capabilities
    struct concept {

        virtual bool equal(const void* other) = 0;
        virtual bool less(const void* other) = 0;
        virtual const void* address() const = 0;
        virtual const std::type_info& type() const = 0;
        virtual std::size_t hash() const = 0;
        virtual std::ostream& emit(std::ostream&) const = 0;
        virtual std::unique_ptr<concept> clone() const = 0;
        virtual ~concept() = default;
    };

    using ptr_type = std::unique_ptr<concept>;

    /// model<> models the concept for any key which supports the required operations
    template<class T>
    struct model : concept {
        model(T&& t) : _t(std::move(t)) {}

        bool equal(const void* other) override {
            return _t == (*reinterpret_cast<const T*>(other));
        }

        bool less(const void* other) override {
            return _t < (*reinterpret_cast<const T*>(other));
        }

        const void* address() const override {
            return std::addressof(_t);
        }
        const std::type_info& type() const override {
            return typeid(_t);
        }

        std::size_t hash() const override {
            return std::hash<T>()(_t);
        }

        std::ostream& emit(std::ostream& os) const override
        {
            return os << _t;
        }

        virtual std::unique_ptr<concept> clone() const override
        {
            return std::make_unique<model>(*this);
        }


        T _t;
    };

    template<class T>
    poly_key(T t) : _impl(std::make_unique<model<T>>(std::move(t))) {}

    std::size_t hash() const {
        return _impl->hash();
    }

    bool operator==(const poly_key& r) const {
        return _impl->type() == r._impl->type()
        && _impl->equal(r._impl->address());
    }

    bool operator<(const poly_key& r) const {
        auto& lt = _impl->type();
        auto& rt = r._impl->type();

        if (lt.before(rt)) {
            return true;
        }
        else if (rt.before(lt)) {
            return false;
        }
        else {
            return _impl->less(r._impl->address());
        }
    }

    poly_key(const poly_key& r)
    : _impl(r._impl->clone())
    {

    }

    poly_key(poly_key&& r)
    : _impl(std::move(r._impl))
    {

    }

    friend std::ostream& operator<<(std::ostream& os, const poly_key& k)
    {
        return k._impl->emit(os);
    }

    ptr_type _impl;
};

/// make it hashable
namespace std {
    template<> struct hash<::poly_key> {
        bool operator()(const ::poly_key& r) const {
            return r.hash();
        }
    };
}

//
// test
//
int main()
{
    std::unordered_map<poly_key, std::string> m;

    m.emplace(poly_key(std::string("key 1")), "Hello");
    m.emplace(poly_key(2), "World");

    std::cout << "unordered:\n";
    for (auto& e : m) {
        std::cout << e.first << " : " << e.second << std::endl;
    }

    std::cout << "\nordered:\n";
    std::map<poly_key, std::string> m2 (m.begin(), m.end());
    for (auto& e : m2) {
        std::cout << e.first << " : " << e.second << std::endl;
    }   
}

示例输出(顺序可能因工具集而异):

unordered:
2 : World
key 1 : Hello

ordered:
key 1 : Hello
2 : World

【讨论】:

    【解决方案5】:

    为您的类型指定一个名称并将其与您的地图一起使用:

    #include<map>
    #include<cassert>
    
    struct B { static int cnt; };
    int B::cnt = 0;
    
    template<typename T>
    struct D: B { static const int type; };
    
    template<typename T>
    const int D<T>::type = B::cnt++;
    
    std::map<int, int> values;
    
    template<typename T>
    void set(int value) { values[D<T>::type] = value; }
    
    template<typename T>
    int get() { return values[D<T>::type]; }
    
    struct T1 { };
    struct T2 { };
    
    int main() {
        set<T1>(42);
        set<T2>(0);
        assert(get<T1>() == 42);
        assert(get<T2>() == 0);
        set<T2>(3);
        assert(get<T2>() == 3);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-06
      • 2011-06-23
      • 2011-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多