2016-06-21 2 views
6

Мне интересно, можно ли использовать шаблон в качестве ключа для карты. Например:Использование шаблонов в качестве ключей в std :: map

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

В сущности, то, что я хочу сделать, это быть в состоянии иметь кучу узлов, содержащих произвольные типы данных, и сконструированы по этим данным. Я думаю, что я мог бы сделать это, преобразовывая указанные данные в двоичные и ключевые данные, но это грязно, и я хочу избежать этого.

Чтобы уточнить, я хочу иметь возможность использовать переменную любого типа в качестве ключа. Например, если у меня есть 2 узла, один из которых содержит int как свои данные, а другой, который содержит Foo в качестве своих данных, я хочу, чтобы их можно было помещать на той же карте, используя свои данные в качестве ключа. Возможно, карта не то, что я хочу, я не уверен ...

Мысли? Спасибо!

+3

Чтобы создать экземпляр этого класса, вам необходимо выбрать определенный тип. Однако вы можете взглянуть на Boost Any: http://www.boost.org/doc/libs/1_61_0/doc/html/any.html. –

+0

Непонятно, что вы пытаетесь сделать – Slava

+0

Время выполнения или время компиляции? Если время компиляции, вы не ищете шаблон constexpr ...? Если runtime, typeid, как показано ниже. – lorro

ответ

2

То, что я обычно делаю это:

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; 

Тогда он будет работать до тех пор, пока карта может хешировать.

+0

Можете ли вы уточнить, что это такое, я не знаком с этим. – vtleavs

+1

@vtleavs тип, способный удерживать значение любого типа. [Документация] (http://en.cppreference.com/w/cpp/utility/any) –

+1

Я сделал ссылку на стандартную библиотечную документацию. Я могу вставить его снова здесь, если вы хотите: ['std :: any'] (http://en.cppreference.com/w/cpp/experimental/any) –

2

Я думаю, что для этой цели гораздо проще использовать std::type_info для типов:

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

Но это действительно зависит от того, что вы хотите достичь, пока неясно мне. Надеюсь, что поможет

+0

Если непонятно, в чем вопрос, как вы можете ответить? – Slava

+0

@HolyBlackCat: Он действительно компилируется с моим gcc 5, может быть, вы используете старый компилятор? Также документация явно указывает на использование std :: type_info, [здесь] (http://en.cppreference.com/w/cpp/language/typeid) – nogard

+0

Использует ли type_index этот тип в качестве ключа или использует ключи любого тип? Я ищу, чтобы иметь возможность использовать данные любого типа в качестве ключа, а не самого типа. – vtleavs

1

По крайней мере, если я понимаю, чего вы хотите, короткий ответ - нет.

Ключи на карте должны быть заказаны - то есть вы должны определить для любой пары ключей A и B, вы должны определить упорядоченное соотношение, где либо A меньше B, либо B меньше, чем A, или два ключа эквивалентны.

Учитывая два ключа совершенно произвольных типов, не будет определенного способа их сравнения. Поэтому вы не можете использовать их в качестве ключей для карты.

Чтобы получить что-то близкое, вам необходимо определить определенный набор типов, которые вы хотите поддерживать. Затем вы можете определить что-то, что (примерно) объединение всех типов, которые вы хотите поддерживать. Одного этого будет недостаточно, но вам также придется определять порядок. В зависимости от того, что вы пытаетесь выполнить, у вас может быть (например) идентификатор в каждом из этих объектов и заказать их по идентификатору. В качестве альтернативы вы можете определить порядок между объектами, поэтому (например) каждый Person сортирует перед каждым Dog, который сортируется перед каждым Dream и так далее. Затем вам нужно будет определить порядок в каждом типе, как обычно.

Я бы предупредил, однако, что это, как правило, требует довольно много дополнительной работы и обеспечивает очень мало значения взамен. Я бы сказал, что более 90% случаев я видел людей (стараюсь) делать это, это была ошибка, которая в лучшем случае работала плохо. Если это вообще возможно, я попытаюсь найти другой подход к любой проблеме, которую вы пытаетесь решить.

+0

Я думаю, что возможно, например, сравнить данные по их двоичным значениям, но я не уверен, могу ли я это сделать. – vtleavs

+0

@vtleavs: Нет, не значимо.Например, он не будет корректно обрабатывать значения с плавающей запятой - они имеют как отрицательный ноль, так и положительный ноль, которые должны сравниваться друг с другом, но если вы посмотрите на биты, они будут разными. –

1

Вы можете создать класс, похожий на poly_key, который будет принимать любой тип условия, что он:

  • копируемый или движимый (копируемый для этой демонстрации)

  • равенства сопоставимый (если он используется в неупорядоченная карта)

  • меньше, чем сравнимы (если они используются в карте)

  • hashable (если они используются в упорядоченной карте)

  • 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 
0

Дайте имя вашим типам и потребителям e it с вашей картой:

#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); 
} 
Смежные вопросы