Вывод типов аргументов шаблона шаблона C ++

10

У меня есть код, который находит и распечатывает совпадения шаблона, как проходя через контейнер строк. Печать выполняется в функции foo, которая является шаблонной.

Код

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

При компиляции у меня возникает ошибка, что вывод типов завершился неудачей из-за несогласованности предоставляемых итераторов, их типы оказываются разнообразными.

Ошибка компиляции 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.

Выход 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)

Что я не ловлю? Является ли мое использование вывода шаблонных шаблонов неправильным и является ли это злоупотреблением с точки зрения стандарта? Ни g ++ - 9.2 с listdc ++ 11, ни clang ++ с libc ++ не могут скомпилировать это.

dannftk
источник
1
Он работает на GCC с -std=c++17и на Clang с -std=c++17-frelaxed-template-template-argsфлагом. В противном случае кажется, что вам нужен другой параметр шаблона для распределителя.
HolyBlackCat
@HolyBlackCat, действительно, спасибо
dannftk

Ответы:

10

Ваш код должен работать нормально, начиная с C ++ 17. (Он компилируется с gcc10 .)

Аргумент шаблона шаблона std::vectorимеет два параметра шаблона (2-й имеет аргумент по умолчанию std::allocator<T>), но параметр шаблона шаблона Containerимеет только один. Начиная с C ++ 17 ( CWG 150 ), аргументы шаблона по умолчанию разрешены для аргумента шаблона шаблона, чтобы соответствовать параметру шаблона шаблона с меньшим количеством параметров шаблона.

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

До C ++ 17 вы можете определить 2-й параметр шаблона с аргументом по умолчанию для параметра шаблона шаблона Container, например

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

Или примените пакет параметров .

template<typename Iterator, template<typename...> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)
songyuanyao
источник
1

В некоторых версиях C ++ Containerне может совпадать std::vector, потому что на std::vectorсамом деле не является template <typename> class. Это template <typename, typename> classгде второй параметр (тип распределителя) имеет аргумент шаблона по умолчанию.

Хотя это может сработать, чтобы добавить другой параметр шаблона, typename Allocсделать параметр функции Container<std::pair<Iterator, Iterator>, Alloc>, это может быть проблемой для других типов контейнеров.

Но так как ваша функция на самом деле не использует параметр шаблона шаблона Container, нет необходимости требовать такого сложного вывода аргумента шаблона со всеми хитростями и ограничениями вывода аргумента шаблона шаблона:

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

Это также не требует Iteratorвывода как один и тот же тип в трех разных местах. Это означает, что будет допустимо передать X::iteratoras firstи контейнер, содержащий X::const_iteratorили наоборот, и вывод аргумента шаблона все еще может быть успешным.

Один небольшой недостаток заключается в том, что если другой шаблон использует методы SFINAE, чтобы попытаться определить, действительна ли подпись foo, это объявление будет соответствовать почти чему-либо, например foo(1.0, 2). Это часто не важно для функции специального назначения, но приятно быть более ограничительным (или «дружественным по отношению к SFINAE»), по крайней мере, для функций общего назначения. Мы могли бы добавить основное ограничение, например:

// 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
источник
На самом деле я всегда хочу убедиться, что контейнер, предоставленный в параметрах, передает значения в виде std :: pair итераторов, имеющих тип первого параметра, поэтому первое упрощение предложенной вами функции шаблона не может соответствовать моим требованиям, в отличие от к этому второму подойдет ваше решение с SFINAE. Во всяком случае, большое спасибо
Dannftk