Desambigua a especialização de gabarito entre recipientes semelhantes a mapa e semelhantes a vetores

template struct Printer; // I want this to match std::vector (and similar linear containers) template<template class T, class TV, class... TS> struct Printer<T> { ... }; // I want this to match std::map (and similar map-like containers) template<template class TM, class TK, class TV, typename... TS> struct Printer<TM> { ... } int main() { // Both of these match the second specialization, which is only intended // for std::map (and similar map-like containers) Printer<std::vector>::something(); Printer<std::map>::something(); } 

Como você pode ver no exemplo, std::vector e std::map combinam a segunda especialização. Eu acho que é porque o parâmetro allocator do std::vector é correspondido ao TV , que é destinado ao valor do std::map .

Como posso corresponder std::vector (e outros containers lineares) com a primeira especialização e std::map (e outros containers de valor-chave) com o segundo?

O problema com a abordagem de correspondência de padrões é que ela só funcionará se, para cada recipiente, você escrever uma especialização. Este é um trabalho tedioso.

Em vez disso, você pode confiar em outras propriedades:

  • um contêiner será necessariamente iterável por meio de expressões begin(c) e end(c)
  • Além disso, um contêiner associativo terá um tipo nested ::key_type , entre outros, conforme expresso em § 23.2.4 [associative.rqmts] .

Portanto, podemos criar um classificador baseado no despacho de tags :

 inline constexpr auto is_container_impl(...) -> std::false_type { return std::false_type{}; } template  constexpr auto is_container_impl(C const* c) -> decltype(begin(*c), end(*c), std::true_type{}) { return std::true_type{}; } template  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  constexpr auto is_associative_container_impl(C const*) -> std::true_type { return std::true_type{}; } template  constexpr auto is_associative_container(C const& c) -> decltype(is_associative_container_impl(&c)) { return is_associative_container_impl(&c); } 

E agora você pode escrever código “simples”:

 template  void print_container(C const& c, std::false_type/*is_associative*/) { } template  void print_container(C const& c, std::true_type/*is_associative*/) { } template  void print_container(C const& c) { return print_container(C, is_assocative_container(c)); } 

Agora, isso pode não ser exatamente o que você deseja, porque sob esses requisitos, um set é um contêiner associativo, mas seu valor não é um pair , portanto, não é possível imprimir key: value . Você precisa adaptar o despacho de tags às suas necessidades.

Sua pergunta é um pouco ambígua, pois há também contêineres que não são sequenciais, nem “key-value”, por exemplo, set . Eu entendo que você quis distinguir seqüência de recipientes associativos?

Se esse for o caso, você pode confiar no fato de que os contêineres associativos têm key_type , enquanto os contêineres de sequência não. Aqui está uma solução:

 #include  #include  #include  template struct IsAssociativeContainer : std::false_type {}; template struct IsAssociativeContainer::type> : std::true_type {}; template::value> struct Printer; // I want this to match std::vector (and similar linear containers) template class T, class TV, class... TS> struct Printer, false> { static void something(); }; // I want this to match std::map (and similar map-like containers) template class TM, class TK, class TV, typename... TS> struct Printer, 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>::something(); Printer>::something(); } 

Exemplo ao vivo

O problema aqui é que

 template  T 

e

 template  TM 

ambos correspondem a qualquer class de modelo que tenha pelo menos dois parâmetros de modelo, o que é o caso em ambos os exemplos. Uma coisa que você pode fazer é tornar as duas listas de parâmetros de modelo mais específicas, como por exemplo:

 template  struct Printer; template  class C, template  class A, typename T> struct Printer< C> > { ... }; template  class C, template  class Comp, template  class A, typename K, typename T> struct Printer< C, A>> > { ... }; 

Você pode vê-lo trabalhando para std :: vector e std :: map aqui: http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9

Outra possibilidade é usar o SFINAE (na verdade, eu recomendaria usá-lo em ambos os cenários):

 template class T, class TV, class... TS, class = typename std::enable_if::value>::type> struct Printer> { ... }; template class TM, class TK, class TV, typename... TS, class = typename std::enable_if::value>::type> struct Printer> { ... } 

Edit: Oups, acabou de ler nos comentários que você queria combinar algo como ‘std :: vector’, não especificamente std :: vector. O primeiro método, no entanto, deve pelo menos diferenciar entre std :: vector e std :: map. Se você quiser escrever algoritmos para contêineres com maneiras diferentes de iterar, por que não escrever suas funções para iteradores e diferenciar entre elas?

Edit2: O código antes estava miseravelmente errado. No entanto, funciona agora.