Deducción de tipos de argumentos de plantilla de plantilla C ++

10

Tengo un código que encuentra e imprime las coincidencias de un patrón que va sobre el contenedor de cadenas. La impresión se realiza en la función foo que tiene la plantilla

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

Al compilar, tengo un error de que la deducción de tipos ha fallado debido a la inconsistencia de los iteradores proporcionados, sus tipos resultan ser diversos.

Error de compilación de 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.

Salida 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é no estoy captando? ¿Mi utilización de la deducción de tipos de plantilla de plantilla es incorrecta y parece un abuso desde el punto de vista del estándar? Ni g ++ - 9.2 con listdc ++ 11 ni clang ++ con libc ++ pueden compilar esto.

dannftk
fuente
1
Funciona en GCC con -std=c++17y en Clang con -std=c++17-frelaxed-template-template-argsbandera. De lo contrario , parece que necesita otro parámetro de plantilla para el asignador.
HolyBlackCat
@HolyBlackCat, de hecho, gracias
dannftk

Respuestas:

10

Su código debería funcionar bien desde C ++ 17. (Se compila con gcc10 .)

El argumento de plantilla de plantilla std::vectortiene dos parámetros de plantilla (el segundo tiene un argumento predeterminado std::allocator<T>), pero el parámetro de plantilla de plantilla Containertiene solo uno. Desde C ++ 17 ( CWG 150 ), los argumentos de plantilla predeterminados están permitidos para que el argumento de plantilla coincida con el parámetro de plantilla de plantilla con menos parámetros de plantilla.

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 de C ++ 17, puede definir el segundo parámetro de plantilla con un argumento predeterminado para el parámetro de plantilla de plantilla Container, p. Ej.

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

O aplique el paquete de parámetros .

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

En algunas versiones de C ++, Containerno puede coincidir std::vector, porque en std::vectorrealidad no es un template <typename> class. Es un lugar template <typename, typename> classdonde el segundo parámetro (el tipo de asignador) tiene un argumento de plantilla predeterminado.

Aunque podría funcionar agregar otro parámetro de plantilla para typename Allochacer que el parámetro de función Container<std::pair<Iterator, Iterator>, Alloc>, eso podría ser un problema para otros tipos de contenedor.

Pero dado que su función en realidad no utiliza el parámetro de plantilla de plantilla Container, no es necesario exigir una deducción de argumento de plantilla tan complicada, con todas las trampas y limitaciones de deducir un argumento de plantilla de plantilla:

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

Esto tampoco requiere Iteratorser deducido exactamente como el mismo tipo en tres lugares diferentes. Lo que significa que será válido pasar un X::iteratoras firsty un contenedor que contengaX::const_iterator o viceversa, y la deducción de argumentos de plantilla aún podría tener éxito.

El único inconveniente es que si otra plantilla usa técnicas SFINAE para tratar de determinar si una firma fooes válida, esa declaración coincidiría con casi cualquier cosa, como foo(1.0, 2). Esto a menudo no es importante para una función de propósito específico, pero es bueno ser más restrictivo (o "compatible con SFINAE") al menos para funciones de propósito general. Podríamos agregar una restricción básica con 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
fuente
En realidad, siempre quiero asegurarme de que el contenedor proporcionado en los parámetros transmite los valores como std :: par de iteradores que tienen el tipo del primer parámetro, por lo tanto, la primera simplificación de la función de plantilla que ofreció no parece cumplir con mis requisitos, al contrario a esto, lo segundo que hará su solución con SFINAE. De todos modos, muchas gracias
dannftk