2016-02-09 18 views
11

A volte trovo la necessità di scrivere routine generali che possono essere applicate a un contenitore di oggetti o una mappa di tali contenitori (ad esempio, elaborare ciascun contenitore nella mappa). Un approccio è quello di scrivere routine separate per i tipi di carta, ma penso che possa essere più naturale e meno prolissa di avere una routine che funziona per entrambi i tipi di input:Controllare se un tipo è una mappa

template <typename T> 
auto foo(const T& items) 
{ 
    return foo(items, /* tag dispatch to map or non-map */); 
} 

Che cosa è un modo sicuro e pulito per fare questa spedizione di tag?

+2

controllare se essa è annidato 'key_type' e' tipi mapped_type'? –

risposta

12

Il test risposte esistente per le proprietà molto specifiche di std::map, o che è proprio una specializzazione di std::map (che sarebbe falsa per std::unordered_map o tipi non standard con la stessa interfaccia di std::map), o testare che il suo value_type è esattamente std::pair<const key_type, mapped_type> (che sarebbe vero per multimap e unordered_map, ma falso per tipi non standard con interfacce simili).

Ciò solo prove che essa fornisce key_type e mapped_type membri, e si può accedere con operator[], quindi non dire che è std::multimap mappish:

#include <type_traits> 

namespace detail { 
    // Needed for some older versions of GCC 
    template<typename...> 
    struct voider { using type = void; }; 

    // std::void_t will be part of C++17, but until then define it ourselves: 
    template<typename... T> 
    using void_t = typename voider<T...>::type; 

    template<typename T, typename U = void> 
    struct is_mappish_impl : std::false_type { }; 

    template<typename T> 
    struct is_mappish_impl<T, void_t<typename T::key_type, 
            typename T::mapped_type, 
            decltype(std::declval<T&>()[std::declval<const typename T::key_type&>()])>> 
    : std::true_type { }; 
} 

template<typename T> 
struct is_mappish : detail::is_mappish_impl<T>::type { }; 

Poiché is_mappish ha una "base caratteristica" di uno true_type o false_type è possibile inviare su di esso in questo modo:

template <typename T> 
auto foo(const T& items, true_type) 
{ 
    // here be maps 
} 

template <typename T> 
auto foo(const T& items, false_type) 
{ 
    // map-free zone 
} 

template <typename T> 
auto foo(const T& items) 
{ 
    return foo(items, is_mappish<T>{}); 
} 

oppure si può evitare di dispacciamento del tutto, e solo il sovraccarico foo per le mappe e non-mappe:

template <typename T, 
      std::enable_if_t<is_mappish<T>{}, int> = 0> 
auto foo(const T& items) 
{ 
    // here be maps 
} 

template <typename T, 
      std::enable_if_t<!is_mappish<T>{}, int> = 0> 
auto foo(const T& items) 
{ 
    // map-free zone 
} 
+0

'is_mappish_impl' mi ha fatto ridere. :) – erip

+0

sebbene, questa soluzione sia valida anche solo quando la mappa non standard implementa l'interfaccia del contenitore associativo standard, anche questa potrebbe interrompersi. –

+2

@DavidHaim, sì, ovviamente. Se il tipo non implementa un'interfaccia specifica, probabilmente non si desidera inviare a una funzione modello che si basa su tale interfaccia. Il tratto può essere facilmente adattato per verificare quali parti dell'interfaccia della mappa sono rilevanti per il 'foo' dell'OP che funziona con le mappe, poiché qualsiasi definizione di" mappa "è pertinente alla funzione. –

1

Ecco cosa mi è venuta:

#include <type_traits> 
#include <utility> 

namespace detail 
{ 
    template <typename T, typename = void> 
    struct IsMap : std::false_type {}; 

    template <typename T> 
    struct IsMap<T, std::enable_if_t< 
         std::is_same<typename T::value_type, 
            std::pair<const typename T::key_type, 
               typename T::mapped_type> 
         >::value> 
    > : std::true_type {}; 
} 

template <typename T> 
constexpr bool is_map = detail::IsMap<T>::value; 

namespace { template <bool> struct MapTagImpl {}; } 
using MapTag = MapTagImpl<true>; 
using NonMapTag = MapTagImpl<false>; 

template <typename T> 
using MapTagType = MapTagImpl<is_map<T>>; 
+1

Derivare da 'true_type' o' false_type' è più semplice della definizione di 'valore booster statico constexpr'. Anche il tuo tratto è vero per multimaps, che potrebbe non essere desiderabile (non hanno la stessa interfaccia). –

+0

@JonathanWakely I buoni punti, hanno una risposta aggiornata. Grazie! – Daniel

10

Questo ha funzionato per me, non testato al 100% però:

template <class T> 
struct isMap { 
    static constexpr bool value = false; 
}; 

template<class Key,class Value> 
struct isMap<std::map<Key,Value>> { 
    static constexpr bool value = true; 
}; 

int main() { 
    constexpr bool b1 = isMap<int>::value; //false 
    constexpr bool b2 = isMap<std::vector<int>>::value; //false 
    constexpr bool b3 = isMap<std::map<int,std::string>>::value; //true 
    constexpr bool b4 = isMap<std::future<int>>::value; //false 
} 
+2

Vale la pena notare che questo funziona solo per 'std :: map' e non per gli altri tipi di 'map' (ad esempio' std :: unordered_map'). Probabilmente avrei dovuto essere più specifico nella domanda. – Daniel

+0

@Daniel quindi aggiungi una specializzazione per unordered_map se questo è necessario – Laurijssen

+0

@Daniel. Stavo assumendo che tu parlassi di una normale mappa standard. questo può essere facilmente convertito per supportare std :: unordered_map, e successivamente isAnyMap. anche se dubito che qualcuno utilizzi più di 1-2 tipi di mappe nello stesso progetto .. –

Problemi correlati