Deduzione dei tipi di argomenti del modello di modello C ++


10

Ho un codice che trova e stampa le corrispondenze di uno schema mentre passa sopra il contenitore di stringhe. La stampa viene eseguita nella funzione foo che è modellata

Il codice

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

Durante la compilazione si è verificato un errore in base al quale la deduzione dei tipi non è riuscita a causa dell'inconsistenza degli iteratori forniti, i loro tipi risultano diversi.

Errore di compilazione 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.

L' output di 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)

Cosa non sto prendendo? Il mio utilizzo dei tipi di modello di template dedotto è errato e appare un abuso dal punto di vista dello standard? Né g ++ - 9.2 con listdc ++ 11clang ++ con libc ++ sono in grado di compilare questo.


1
Funziona su GCC con -std=c++17e su Clang con -std=c++17-frelaxed-template-template-argsbandiera. Altrimenti sembra che sia necessario un altro parametro modello per l'allocatore.
HolyBlackCat

@HolyBlackCat, anzi, grazie
dannftk

Risposte:


10

Il tuo codice dovrebbe funzionare bene dal C ++ 17. (Si compila con gcc10 .)

L'argomento template modello std::vectorha due parametri template (il secondo ha argomento predefinito std::allocator<T>), ma il parametro template modello ne Containerha solo uno. A partire da C ++ 17 ( CWG 150 ), gli argomenti modello predefiniti sono consentiti affinché l' argomento modello modello corrisponda al parametro modello modello con un numero inferiore di parametri modello.

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

Prima di C ++ 17, è possibile definire il secondo parametro del modello con argomento predefinito per il parametro del modello del modello Container, ad es

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

Oppure applica il pacchetto di parametri .

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

1

In alcune versioni di C ++, Containernon può corrispondere std::vector, perché in std::vectorrealtà non è un template <typename> class. È un punto in template <typename, typename> classcui il secondo parametro (il tipo di allocatore) ha un argomento template predefinito.

Sebbene possa funzionare per aggiungere un altro parametro modello, typename Allocrendere il parametro funzione Container<std::pair<Iterator, Iterator>, Alloc>, potrebbe essere un problema per altri tipi di contenitore.

Ma dal momento che la tua funzione non utilizza effettivamente il parametro template template Container, non è necessario richiedere una deduzione dell'argomento template così complicata, con tutti i trucchi e le limitazioni di dedurre un argomento template template:

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

Inoltre, non è necessario Iteratordedurre lo stesso tipo esatto in tre luoghi diversi. Ciò significa che sarà valido passare un contenitore X::iteratoras firste un contenitore contenente X::const_iteratoro viceversa e la deduzione dell'argomento modello potrebbe comunque riuscire.

L'unico piccolo inconveniente è che se un altro modello utilizza le tecniche SFINAE per provare a determinare se una firma di fooè valida, quella dichiarazione corrisponderebbe a quasi tutto, come foo(1.0, 2). Questo spesso non è importante per una funzione specifica, ma è bello essere più restrittivi (o "compatibili con SFINAE") almeno per le funzioni generali. Potremmo aggiungere una limitazione di base con qualcosa del tipo:

// 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>>>;

In realtà voglio sempre assicurarmi che il contenitore fornito nei parametri trasmetta valori come std :: pair di iteratori che hanno il tipo del primo parametro, quindi la prima semplificazione della funzione template che hai offerto non sembra soddisfare i miei requisiti, al contrario a questo il secondo che farà la tua soluzione con SFINAE. Comunque, grazie mille
dannftk
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.