Considérez cette fonction:
template<template<class, class> class C, class T, class Alloc> void foo(C<T, Alloc>& container) { std::cout << container.size() << std::endl; }
La fonction foo ()
accepte un std :: vector
, mais il accepte également std :: map
en C ++ 17 car Compare
et Allocator
ont des valeurs par défaut. Remarque std :: map
échoue dans C ++ 14.
Comment puis-je faire en sorte que foo ()
n'accepte que des modèles avec exactement 2 paramètres de modèle, donc ça échoue avec std :: map
en C ++ 17?
3 Réponses :
Créez une surcharge de modèle de la fonction qui prend un conteneur avec trois éléments. Lorsque vous essayez d'utiliser un conteneur avec deux paramètres, cela fonctionnera, lorsque vous essayez d'utiliser quelque chose avec trois paramètres avec le troisième ayant une valeur par défaut (comme std :: map
), cela échouera (car soit on pourrait le surcharger).
template <class A, class B, class C> bar{};
notez que cela ne fonctionne pas si quelqu'un entre quelque chose comme
#include <map> #include <vector> #include <iostream> template<template<class, class, class> class C, class Key, class T, class Alloc> void foo(C<Key, T, Alloc>& container) { std::cout << "three arguments" << std::endl; } template<template<class, class> class C, class T, class Alloc> void foo(C<T, Alloc>& container) { std::cout << "two arguments" << std::endl; } int main() { std::vector<int> v; std::map<int,int> m; foo(v); foo(m); }
car cela ne correspondra que option à trois paramètres, donc ce ne sera pas ambigu
Votre solution fonctionne avec g ++ mais ne se compile pas avec clang ++. Je ne sais pas qui a raison mais, pour travailler avec les deux compilateurs, il semble suffisant d'ajouter une classe ...
dans la signature template-template pour la fonction à trois cas (je veux dire: template classe C, clé de classe, classe T, classe Alloc>
)
Si vous voulez éviter que
foo ()
accepte un conteneur acceptant trois paramètres de modèle ou plus (donc échouez avecstd :: map
) est relativement simple: vous pouvez suivre la suggestion du rtpax ou, étant donné des traits de type personnalisés qui indiquent si un type est basé sur un conteneur acceptant deux types#include <map> #include <vector> #include <type_traits> template <typename> struct accept2 : std::false_type { }; template <template <typename...> class C, typename X, typename Y> struct accept2<C<X, Y>> : std::true_type { }; template <typename> struct accept3 : std::false_type { }; template <template <typename...> class C, typename X, typename Y, typename Z> struct accept3<C<X, Y, Z>> : std::true_type { }; template <typename C> std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const &) { } int main() { std::vector<int> v; std::map<int,int> m; foo(v); // compile //foo(m); // compilation error }et des traits de type similaires pour un conteneur acceptant trois types
template <typename C> std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const & container) { }vous pouvez activer SFINAE
foo ()
uniquement si le type déduit accepte deux mais n'accepte pas trois typestemplate <typename> struct accept3 : std::false_type { }; template <template <typename...> class C, typename X, typename Y, typename Z> struct accept3<C<X, Y, Z>> : std::true_type { };Mais cette solution (et aussi celle de rtpax) a un problème: qu'en est-il d'un conteneur qui reçoit avant deux types de modèles et après un (ou plusieurs) paramètres de non-type de modèle avec des valeurs par défaut? p >
Ou deux types de template et un (ou plusieurs) paramètre template-template avec des valeurs par défaut? Peut-être avec des signatures différentes?
Vous pouvez ajouter des spécialisations pour
accept3
pour reconnaître les autres cas, mais il existe des combinaisons infinies de paramètres type, non-type et template-template, avec la valeur par défaut , après les deux premiers types de modèle. Il faut donc écrire des spécialisations infinies pour intercepter tous les cas.C'est un peu peu pratique.
Et je soupçonne qu'il n'y a pas (en C ++ 17) de solution pratique.
Quoi qu'il en soit, un exemple de compilation complet (en évitant au moins trois conteneurs de types de modèles) suit
template <typename> struct accept2 : std::false_type { }; template <template <typename...> class C, typename X, typename Y> struct accept2<C<X, Y>> : std::true_type { };
Ajoutez un niveau d'indirection qui utilise un argument de modèle variadique au lieu de deux fixes. Ensuite, vous pouvez utiliser le bon vieux enable_if
pour le désactiver chaque fois que le nombre n'est pas deux:
template<template<class...> class C, class T, class Alloc> void foo_impl(C<T, Alloc>& container) { std::cout << container.size() << std::endl; } template<template<class...> class C, class... Args> std::enable_if_t<sizeof...(Args) == 2> foo(C<Args...>& container) { foo_impl(container); }
Voir ici comment cela fonctionne.
Remarque: Apparemment, le dernier clang et le le dernier msvc ne le gèrent pas encore correctement. Cependant, clang (concepts expérimentaux) .
Pourquoi voulez-vous? Pourquoi le nombre de paramètres du modèle est-il significatif?
J'ai une surcharge de
foo
avec 4 paramètres de modèle pour correspondre àstd :: map
, maisfoo (std :: map)
est ambigu en c ++ 17.Voulez-vous exclure la
carte
ou toutC<>
avec plus de 2 arguments? Pourquoi voulez-vous faire tout cela de toute façon? Cela ressemble beaucoup à un problème XYNotez que
std :: vector
peut avoir n'importe quel nombre d'arguments de modèle.Je veux exclure tous les
C<>
avec plus de 2 arguments. C'est probablement un problème XY, mais pour faire court, il s'agit de mettre à jour le code tiers.C
doit-il avoir un membresize_t C :: size () const
?Oui, j'attends un conteneur de
std
ou d'une classe ad hoc qui réplique leur interface dans une certaine mesure.Mais pourquoi doit-il éviter la
carte
?@Walter plase voir le deuxième commentaire. J'ai déjà une version de
foo
pourmap
, donc cette autre fonction rend l'appel ambigu.Peut-être simplement désactiver cette fonction particulière (més) jusqu'à ce que le noyau corrige l'ordre partiel.
L'implémentation de cela via le paramètre de modèle de modèle est problématique et totalement inutile. Ne le fais pas. Utilisez plutôt un argument de modèle unique et utilisez SFINAE pour vous spécialiser pour des conteneurs particuliers si vous en avez besoin. Ou mieux encore, écrivez
foo ()
pour prendre des arguments d'itérateur - la méthode typique pour le code générique dans la STL.Fait intéressant, le dernier clang ne compile pas ceci: godbolt.org/z/aln7cD Uniquement les derniers gcc et clang ( concepts expérimentaux) faire.