c++ - template - Desambiguar la especialización de plantillas entre contenedores tipo mapa y vectores.
template function c++ (3)
El problema aquí es que
template <class, class...> T
y
template <class, class, class...> TM
ambas coinciden con cualquier clase de plantilla que tenga al menos 2 parámetros de plantilla, como es el caso en ambos ejemplos. Una cosa que puedes hacer es hacer que las listas de parámetros de la plantilla sean más específicas, como por ejemplo:
template <class>
struct Printer;
template <template<typename, typename> class C, template <typename> class A, typename T>
struct Printer< C<T, A<T>> > {
...
};
template <template<typename, typename, typename, typename> class C, template <typename> class Comp, template <typename> class A, typename K, typename T>
struct Printer< C<K, T, Comp<K>, A<std::pair<const K,T>>> > {
...
};
Puedes verlo trabajando para std :: vector y std :: map aquí: http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9
Otra posibilidad es usar SFINAE (en realidad recomendaría usarlo en ambos escenarios):
template<template<class, class...> class T, class TV, class... TS, class = typename std::enable_if<std::is_same<T, std::vector>::value>::type>
struct Printer<T<TV, TS...>> { ... };
template<template<class, class, class...> class TM, class TK, class TV, typename... TS, class = typename std::enable_if<std::is_same<T, std::map>::value>::type>
struct Printer<TM<TK, TV, TS...>> { ... }
Edit: Oups, solo lee los comentarios que querías que coincidieran con algo como ''std :: vector'', no específicamente std :: vector. Sin embargo, el primer método debería al menos diferenciar entre std :: vector y std :: map. Si desea escribir algoritmos para contenedores con diferentes formas de iterar, ¿por qué no escribir sus funciones para iteradores y diferenciarlas?
Edit2: El código anterior estaba miserablemente mal. Sin embargo funciona ahora.
template<class> struct Printer;
// I want this to match std::vector (and similar linear containers)
template<template<class, class...> class T, class TV, class... TS>
struct Printer<T<TV, TS...>> { ... };
// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS>
struct Printer<TM<TK, TV, TS...>> { ... }
int main()
{
// Both of these match the second specialization, which is only intended
// for std::map (and similar map-like containers)
Printer<std::vector<int>>::something();
Printer<std::map<int, float>>::something();
}
Como puede ver en el ejemplo, std::vector
y std::map
coinciden con la segunda especialización. Creo que es porque el parámetro del asignador de std::vector
hace coincidir con TV
, que está destinado al valor de std::map
.
¿Cómo puedo relacionar std::vector
(y otros contenedores lineales) con la primera especialización y std::map
(y otros contenedores de valor-clave) con el segundo?
El problema con el enfoque de coincidencia de patrones es que solo funcionará si cada contenedor individual escribe una especialización. Este es un trabajo tedioso.
En su lugar puede confiar en otras propiedades:
- un contenedor necesariamente será iterable a través de las expresiones
begin(c)
yend(c)
- Además de esto, un contenedor asociativo tendrá un tipo anidado
::key_type
, entre otros, como se expresa en § 23.2.4 [associative.rqmts] .
Por lo tanto, podemos preparar un clasificador, basado en el envío de etiquetas :
inline constexpr auto is_container_impl(...) -> std::false_type {
return std::false_type{};
}
template <typename C>
constexpr auto is_container_impl(C const* c) ->
decltype(begin(*c), end(*c), std::true_type{})
{
return std::true_type{};
}
template <typename C>
constexpr auto is_container(C const& c) -> decltype(is_container_impl(&c)) {
return is_container_impl(&c);
}
inline constexpr auto is_associative_container_impl(...)
-> std::false_type
{ return std::false_type{}; }
template <typename C, typename = typename C::key_type>
constexpr auto is_associative_container_impl(C const*) -> std::true_type {
return std::true_type{};
}
template <typename C>
constexpr auto is_associative_container(C const& c)
-> decltype(is_associative_container_impl(&c))
{
return is_associative_container_impl(&c);
}
Y ahora puedes escribir código "simple":
template <typename C>
void print_container(C const& c, std::false_type/*is_associative*/) {
}
template <typename C>
void print_container(C const& c, std::true_type/*is_associative*/) {
}
template <typename C>
void print_container(C const& c) {
return print_container(C, is_assocative_container(c));
}
Ahora, esto podría no ser exactamente lo que desea, porque según estos requisitos, un set
es un contenedor asociativo, pero su valor no es un pair
, por lo que no puede imprimir key: value
. Tienes que adaptar la etiqueta-despacho a tus necesidades.
Su pregunta es un poco ambigua, ya que también hay contenedores que no son secuenciales, ni "clave-valor", por ejemplo, set
. Supongo que querías distinguir la secuencia de los contenedores asociativos.
Si ese es el caso, puede confiar en el hecho de que los contenedores asociativos tienen key_type
, mientras que los contenedores de secuencia no. Aquí hay una solución:
#include <type_traits>
#include <vector>
#include <map>
template<class, class = void>
struct IsAssociativeContainer
: std::false_type {};
template<class T>
struct IsAssociativeContainer<T,
typename std::enable_if<sizeof(typename T::key_type)!=0>::type>
: std::true_type {};
template<class T, bool = IsAssociativeContainer<T>::value>
struct Printer;
// I want this to match std::vector (and similar linear containers)
template<template<class, class...> class T, class TV, class... TS>
struct Printer<T<TV, TS...>, false> { static void something(); };
// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS>
struct Printer<TM<TK, TV, TS...>, true> { static void something(); };
int main()
{
// Both of these match the second specialization, which is only intended
// for std::map (and similar map-like containers)
Printer<std::vector<int>>::something();
Printer<std::map<int, float>>::something();
}