Dedução de tipos de argumento do modelo C ++

10

Eu tenho um código que localiza e imprime correspondências de um padrão como passando por cima do contêiner de strings. A impressão é realizada na função foo que é templated

O código

#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;
}

Ao compilar, recebi um erro de que a dedução de tipos falhou devido à inconsistência de iteradores sendo fornecidos, seus tipos acabam sendo diversos.

Erro de compilação do 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.

Saída 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)

O que eu não estou pegando? Minha utilização da dedução de tipos de modelo está errada e parece um abuso do ponto de vista do padrão? Nem o g ++ - 9.2 com listdc ++ 11 nem o clang ++ com libc ++ são capazes de compilar isso.

dannftk
fonte
11
Funciona no GCC com -std=c++17e no Clang com -std=c++17-frelaxed-template-template-argsflag. Caso contrário , parece que você precisa de outro parâmetro de modelo para o alocador.
HolyBlackCat 29/03
@HolyBlackCat, de fato, obrigado
dannftk 29/03

Respostas:

10

Seu código deve funcionar bem desde o C ++ 17. (Compila com gcc10 .)

O argumento do modelo std::vectortem dois parâmetros de modelo (o segundo tem argumento padrão std::allocator<T>), mas o parâmetro do modelo Containertem apenas um. Desde C ++ 17 ( CWG 150 ), os argumentos do modelo padrão são permitidos para que o argumento do modelo corresponda ao parâmetro do modelo com menos parâmetros do modelo.

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

Antes do C ++ 17, você pode definir o segundo parâmetro do modelo com argumento padrão para o parâmetro do modelo Container, por exemplo

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 aplique o pacote de parâmetros .

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

Em algumas versões do C ++, Containernão pode corresponder std::vector, porque std::vectorna verdade não é um template <typename> class. É template <typename, typename> classonde o segundo parâmetro (o tipo de alocador) tem um argumento de modelo padrão.

Embora possa funcionar para adicionar outro parâmetro de modelo ao parâmetro de typename Allocfunção Container<std::pair<Iterator, Iterator>, Alloc>, isso pode ser um problema para outros tipos de contêiner.

Mas como sua função não usa o parâmetro de modelo de modelo Container, não há necessidade de exigir uma dedução de argumento de modelo tão complicada, com todas as vantagens e limitações de deduzir um argumento de modelo de modelo:

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

Isso também não precisa Iteratorser deduzido como exatamente o mesmo tipo em três locais diferentes. Significando que será válido passar um X::iteratoras firste um contêiner contendo X::const_iteratorou vice-versa, e a dedução do argumento do modelo ainda poderá ser bem-sucedida.

A única desvantagem é que, se outro modelo usar técnicas SFINAE para tentar determinar se uma assinatura fooé válida, essa declaração corresponderá a quase qualquer coisa, como foo(1.0, 2). Isso geralmente não é importante para uma função de finalidade específica, mas é bom ser mais restritivo (ou "compatível com SFINAE") pelo menos para funções de finalidade geral. Poderíamos adicionar uma restrição básica com algo como:

// 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
fonte
Na verdade, eu sempre quero garantir que o contêiner fornecido nos parâmetros transmita valores como std :: pair de iteradores que possuem o tipo do primeiro parâmetro, portanto, a primeira simplificação da função de modelo que você ofereceu parece não atender aos meus requisitos, pelo contrário para isso, o segundo que sua solução com SFINAE fará. Enfim, muito obrigado
dannftk 29/03