简体   繁体   中英

Using templates as keys in a std::map

I am wondering whether it is possible to use a template as a key for a map. For instance:

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

In essence, what I want to do is be able to have a bunch of nodes that contain arbitrary types data, and are keyed by that data. I think I could do it by converting said data to binary and keying by that, but that is messy and I want to avoid it.

To clarify, I want to be able to use a variable of any type as the key. For example if I have 2 nodes, one that contains an int as its data, and another that contains a Foo as its data, I want to be able to put those in the same map using their data as the key. Maybe a map is not what I want, I am not sure...

Thoughts? Thanks!

I think for this purpose it's much easier to use std::type_info for the types:

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

But this really depends on what you want to achieve, it's still unclear to me. Hope that helps

What I usually do is this:

template<typename T>
void type_id(){}

using type_id_t = void(*)();

Then, I use it like this:

std::map<type_id_t, Node> nodes;

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

Of course, this can be enhanced with variable template from C++14.


Sorry, I just re-read the question and I understand it better.

What you want is std::any , but it's C++17 only. You can use boost::any instead.

It will look like this:

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

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

It should work as long as the map can somehow order instances of std::any . If not, you can use hash map instead:

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

Then it will work as long as the map can hash an any.

At least if I understand what you want, the short answer is no.

The keys in a map must be ordered--that is, you must define for any pair of keys A and B, you must define an ordered relationship where either A is less than B, or B is less than A, or the two keys are equivalent.

Given two keys of entirely arbitrary types there's not going to be a defined way of comparing them. Therefore, you can't use them as keys in for a map.

To get something sort of close, you'll need to define some specific set of types you want to support. Then you can define something that's (roughly) a union of all the types you want to support. That alone won't be enough though--you'll also have to define ordering. Depending on what you're trying to accomplish, you might have (for example) an ID in every one of those objects, and order them by ID. Alternatively, you might define an ordering between objects, so (for example) every Person sorts before every Dog , which sorts before every Dream , and so on. Then you'll have to define an ordering within each type about like you usually would.

I'd warn, however, that this typically involves quite a bit of extra work, and provides very little value in return. I'd say well over 90% of the time I've seen people (try to) do this, it's been a mistake that worked out poorly at best. If at all possible, I'd try to find some other approach to whatever problem you're trying to solve.

You may create a class similar to poly_key , which will accept any type provided that it is:

  • copyable or moveable (copyable for this demo)

  • equality comparable (if used in unordered map)

  • less-than comparable (if used in map)

  • hashable (if used in ordered map)

  • ostreamable (for this demo)

like so:

#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;
    }   
}

example output (order may vary depending on toolset):

unordered:
2 : World
key 1 : Hello

ordered:
key 1 : Hello
2 : World

Give a name to your types and use it with your map:

#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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM