Déduction des types d'arguments du modèle de modèle C ++

10

J'ai du code qui trouve et imprime les correspondances d'un modèle en passant par le conteneur de chaînes. L'impression est effectuée dans la fonction foo qui est modélisée

Le code

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <string>
#include <tuple>
#include <utility>

template<typename Iterator, template<typename> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
{
    for (auto const &finding : findings)
    {
        std::cout << "pos = " << std::distance(first, finding.first) << " ";
        std::copy(finding.first, finding.second, std::ostream_iterator<char>(std::cout));
        std::cout << '\n';
    }
}

int main()
{
    std::vector<std::string> strs = { "hello, world", "world my world", "world, it is me" };
    std::string const pattern = "world";
    for (auto const &str : strs)
    {
        std::vector<std::pair<std::string::const_iterator, std::string::const_iterator>> findings;
        for (std::string::const_iterator match_start = str.cbegin(), match_end;
             match_start != str.cend();
             match_start = match_end)
        {
            match_start = std::search(match_start, str.cend(), pattern.cbegin(), pattern.cend());
            if (match_start != match_end)
                findings.push_back({match_start, match_start + pattern.size()});
        }
        foo(str.cbegin(), findings);
    }

    return 0;
}

Lors de la compilation, j'ai une erreur indiquant que la déduction des types a échoué en raison de l'incohérence des itérateurs fournis, leurs types se révèlent divers.

Erreur de compilation GCC :

prog.cpp:35:9: error: no matching function for call to 'foo'
        foo(str.cbegin(), findings);
        ^~~
prog.cpp:10:6: note: candidate template ignored: substitution failure [with Iterator = __gnu_cxx::__normal_iterator<const char *, std::__cxx11::basic_string<char> >]: template template argument has different template parameters than its corresponding template template parameter
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
     ^
1 error generated.

Sortie de Clang :

main.cpp:34:9: error: no matching function for call to 'foo'
        foo(str.cbegin(), findings);
        ^~~
main.cpp:9:6: note: candidate template ignored: substitution failure [with Iterator = std::__1::__wrap_iter<const char *>]: template template argument has different template parameters than its corresponding template template parameter
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

Qu'est-ce que je n'attrape pas? Mon utilisation de la déduction des types de modèle de modèle est-elle incorrecte et semble-t-elle un abus du point de vue de la norme? Ni g ++ - 9.2 avec listdc ++ 11 ni clang ++ avec libc ++ ne peuvent le compiler.

dannftk
la source
1
Il fonctionne sur GCC avec -std=c++17et sur Clang avec -std=c++17-frelaxed-template-template-argsdrapeau. Sinon, il semble que vous ayez besoin d'un autre paramètre de modèle pour l'allocateur.
HolyBlackCat
@HolyBlackCat, en effet, merci
dannftk

Réponses:

10

Votre code devrait fonctionner correctement depuis C ++ 17. (Il compile avec gcc10 .)

L'argument de modèle de modèle std::vectora deux paramètres de modèle (le deuxième a un argument par défaut std::allocator<T>), mais le paramètre de modèle de modèle Containern'en a qu'un. Depuis C ++ 17 ( CWG 150 ), les arguments de modèle par défaut sont autorisés pour que l' argument de modèle de modèle corresponde au paramètre de modèle de modèle avec moins de paramètres de modèle.

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };

template<template<class> class P> class X { /* ... */ };

X<A> xa; // OK
X<B> xb; // OK in C++17 after CWG 150
         // Error earlier: not an exact match

Avant C ++ 17, vous pouvez définir le 2ème paramètre de modèle avec un argument par défaut pour le paramètre de modèle de modèle Container, par exemple

template<typename Iterator, template<typename T, typename Alloc=std::allocator<T>> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

Ou appliquez le pack de paramètres .

template<typename Iterator, template<typename...> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
songyuanyao
la source
1

Dans certaines versions de C ++, Containerne peut pas correspondre std::vector, car ce std::vectorn'est pas réellement un template <typename> class. C'est un template <typename, typename> classoù le deuxième paramètre (le type d'allocateur) a un argument de modèle par défaut.

Bien qu'il puisse fonctionner pour ajouter un autre paramètre de modèle typename Allocrendre le paramètre de fonction Container<std::pair<Iterator, Iterator>, Alloc>, cela pourrait être un problème pour d'autres types de conteneurs.

Mais puisque votre fonction n'utilise pas réellement le paramètre de modèle de modèle Container, il n'est pas nécessaire d'exiger une déduction d'argument de modèle aussi compliquée, avec tous les pièges et les limitations de déduire un argument de modèle de modèle:

template<typename Iterator, class Container>
void foo(Iterator first, Container const &findings);

Cela ne nécessite pas non plus Iteratord'être déduit comme le même type exact à trois endroits différents. Cela signifie qu'il sera valide de passer un X::iteratoras firstet un conteneur contenant X::const_iteratorou vice versa, et la déduction d'argument de modèle pourrait toujours réussir.

Le seul léger inconvénient est que si un autre modèle utilise des techniques SFINAE pour essayer de déterminer si une signature de fooest valide, cette déclaration correspondrait à presque n'importe quoi, comme foo(1.0, 2). Ce n'est souvent pas important pour une fonction spécifique, mais il est agréable d'être plus restrictif (ou "compatible SFINAE") au moins pour les fonctions générales. Nous pourrions ajouter une restriction de base avec quelque chose comme:

// Require Container is container-like (including raw array or std::initializer_list)
// and its values have members first and second of the same type,
// which can be compared for equality with Iterator.
template <typename Iterator, class Container>
auto foo(Iterator first, Container const &findings)
    -> std::void_t<decltype(first == std::begin(findings)->first),
           std::enable_if_t<std::is_same_v<std::begin(findings)->first, 
                            std::begin(findings)->second>>>;
aschepler
la source
En fait, je veux toujours m'assurer que le conteneur fourni dans les paramètres transmet des valeurs en tant que std :: paire d'itérateurs qui ont le type du premier paramètre, donc la première simplification de la fonction de modèle que vous avez proposée ne semble pas répondre à mes exigences, en face à cela la seconde que fera votre solution avec SFINAE. Quoi qu'il en soit, merci beaucoup
dannftk