Abzug der Argumenttypen für C ++ - Vorlagenvorlagen

10

Ich habe Code, der Übereinstimmungen eines Musters findet und ausdruckt, indem er über den Container mit Zeichenfolgen geht. Der Druck wird in der Funktion ausgeführt foo , die als Templat wird

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

Beim Kompilieren ist der Fehler aufgetreten, dass der Typabzug aufgrund der Inkonsistenz der bereitgestellten Iteratoren fehlgeschlagen ist. Ihre Typen sind unterschiedlich.

GCC- Kompilierungsfehler:

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.

Clangs Ausgabe:

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)

Was fange ich nicht? Ist meine Verwendung des Abzugs von Vorlagenvorlagentypen falsch und erscheint aus Sicht des Standards als Missbrauch? Weder g ++ - 9.2 mit listdc ++ 11 noch clang ++ mit libc ++ können dies kompilieren.

dannftk
quelle
1
Es funktioniert auf GCC mit -std=c++17und auf Clang mit -std=c++17-frelaxed-template-template-argsFlagge. Andernfalls benötigen Sie anscheinend einen anderen Vorlagenparameter für den Allokator.
HolyBlackCat
@HolyBlackCat, in der Tat, danke
dannftk

Antworten:

10

Ihr Code sollte seit C ++ 17 einwandfrei funktionieren. (Es wird mit gcc10 kompiliert .)

Das Vorlagenvorlagenargument std::vectorenthält zwei Vorlagenparameter (der zweite hat das Standardargument std::allocator<T>), der Vorlagenvorlagenparameter Containerjedoch nur einen. Seit C ++ 17 ( CWG 150 ) sind die Standardvorlagenargumente für Vorlagenvorlagenargumente zulässig, um Vorlagenvorlagenparameter mit weniger Vorlagenparametern abzugleichen.

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

Vor C ++ 17 können Sie den zweiten Vorlagenparameter mit dem Standardargument für den Vorlagenvorlagenparameter definieren Container, z

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

Oder wenden Sie das Parameterpaket an .

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

In einigen Versionen von C ++ Containerkann nicht übereinstimmen std::vector, da std::vectores sich eigentlich nicht um eine handelt template <typename> class. Hier hat template <typename, typename> classder zweite Parameter (der Allokatortyp) ein Standardvorlagenargument.

Obwohl es funktionieren könnte, einen weiteren Vorlagenparameter hinzuzufügen, typename Allocmachen Sie den Funktionsparameter Container<std::pair<Iterator, Iterator>, Alloc>, dies könnte ein Problem für andere Containertypen sein.

Da Ihre Funktion den Vorlagenvorlagenparameter jedoch nicht tatsächlich verwendet, muss kein Containerso komplizierter Abzug von Vorlagenargumenten erforderlich sein, mit allen Fallstricken und Einschränkungen beim Ableiten eines Vorlagenvorlagenarguments:

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

Dies muss auch nicht Iteratoran drei verschiedenen Stellen als exakt der gleiche Typ abgeleitet werden. Das heißt, es ist gültig, ein X::iteratoras firstund einen Container zu übergeben, der enthält X::const_iteratoroder umgekehrt, und der Abzug von Vorlagenargumenten könnte weiterhin erfolgreich sein.

Der einzige kleine Nachteil ist, dass, wenn eine andere Vorlage SFINAE-Techniken verwendet, um festzustellen, ob eine Signatur von foogültig ist, diese Deklaration mit fast allem übereinstimmt, wie z foo(1.0, 2). Dies ist oft nicht wichtig für eine bestimmte Funktion, aber es ist schön, zumindest für allgemeine Funktionen restriktiver (oder "SFINAE-freundlich") zu sein. Wir könnten eine grundlegende Einschränkung hinzufügen mit etwas wie:

// 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
quelle
Eigentlich möchte ich immer sicherstellen, dass der in den Parametern bereitgestellte Container Werte als std :: pair von Iteratoren übermittelt, die den Typ des ersten Parameters haben. Daher kann die erste Vereinfachung der von Ihnen angebotenen Vorlagenfunktion meine Anforderungen nicht erfüllen Dies ist die Sekunde, in der Ihre Lösung mit SFINAE ausreicht. Wie auch immer, vielen Dank
dannftk