Odliczenie typów argumentów szablonu szablonu C ++

10

Mam kod, który wyszukuje i drukuje dopasowania wzorca przechodzące przez kontener ciągów. Drukowanie odbywa się w funkcji foo, która jest szablonowana

Kod

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

Podczas kompilacji wystąpił błąd polegający na tym, że dedukcja typów nie powiodła się z powodu niespójności dostarczanych iteratorów, a ich typy okazują się różnorodne.

Błąd kompilacji 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.

Wyjście Clanga :

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)

Czego nie łapię? Czy moje wykorzystanie dedukcji typów szablonów jest niewłaściwe i wydaje się nadużyciem z punktu widzenia standardu? Ani g ++ - 9.2 z listdc ++ 11, ani clang ++ z libc ++ nie są w stanie tego skompilować.

dannftk
źródło
1
Działa na GCC -std=c++17zi na Clang z -std=c++17-frelaxed-template-template-argsflagą. W przeciwnym razie wydaje się, że potrzebujesz innego parametru szablonu dla alokatora.
HolyBlackCat
@HolyBlackCat, naprawdę, dziękuję
dannftk

Odpowiedzi:

10

Twój kod powinien działać poprawnie od C ++ 17. (Kompiluje się z gcc10 .)

Argument szablonu szablonu std::vectorma dwa parametry szablonu (drugi ma domyślny argument std::allocator<T>), ale parametr szablonu szablonu Containerma tylko jeden. Od C ++ 17 ( CWG 150 ) domyślne argumenty szablonu są dozwolone, aby argument szablonu szablonu był zgodny z parametrem szablonu szablonu z mniejszą liczbą parametrów szablonu.

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

W wersjach wcześniejszych niż C ++ 17 można zdefiniować drugi parametr szablonu z domyślnym argumentem parametru szablonu szablonu Container, np

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

Lub zastosuj pakiet parametrów .

template<typename Iterator, template<typename...> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
songyuanyao
źródło
1

W niektórych wersjach C ++ Containernie można dopasować std::vector, ponieważ std::vectortak naprawdę nie jest template <typename> class. To tutaj template <typename, typename> classdrugi parametr (typ alokatora) ma domyślny argument szablonu.

Chociaż dodanie innego parametru szablonu typename Allocmoże działać, parametr funkcji Container<std::pair<Iterator, Iterator>, Alloc>może być problemem dla innych typów kontenerów.

Ale ponieważ twoja funkcja tak naprawdę nie używa parametru szablonu szablonu Container, nie trzeba wymagać tak skomplikowanego odejmowania argumentu szablonu, z wszystkimi problemami i ograniczeniami dedukcji argumentu szablonu szablonu:

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

Nie wymaga Iteratorto również wywnioskowania jako dokładnie tego samego typu w trzech różnych miejscach. Oznacza to, że poprawne będzie przekazanie X::iteratoras firsti kontenera zawierającego X::const_iteratorlub odwrotnie, a dedukcja argumentów szablonu nadal może się udać.

Jedyną niewielką wadą jest to, że jeśli inny szablon korzysta z technik SFINAE w celu ustalenia, czy podpis foojest prawidłowy, deklaracja będzie pasować prawie do wszystkiego foo(1.0, 2). Często nie jest to ważne dla funkcji określonego celu, ale fajnie jest być bardziej restrykcyjnym (lub „przyjaznym dla SFINAE”) przynajmniej dla funkcji ogólnego przeznaczenia. Możemy dodać podstawowe ograniczenie za pomocą czegoś takiego:

// 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
źródło
Właściwie zawsze chcę mieć pewność, że kontener podany w parametrach przekazuje wartości jako std :: para iteratorów, które mają typ pierwszego parametru, dlatego pierwsze uproszczenie funkcji szablonu, którą oferujesz, nie wydaje się spełniać moich wymagań, wręcz przeciwnie w tym momencie zrobi to twoje rozwiązanie z SFINAE. W każdym razie dziękuję bardzo
dannftk