Вывод типов аргументов шаблона шаблона C ++ - PullRequest
10 голосов
/ 29 марта 2020

У меня есть код, который находит и распечатывает совпадения шаблона при переходе через контейнер строк. Печать выполняется в функции 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;
}

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

G CC Ошибка компиляции:

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 ++ не могут скомпилировать это.

Ответы [ 2 ]

10 голосов
/ 29 марта 2020

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

Аргумент шаблона шаблона std::vector имеет два параметра шаблона (второй имеет аргумент по умолчанию 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)
1 голос
/ 29 марта 2020

В некоторых версиях 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::iterator как first и контейнер, содержащий X::const_iterator или наоборот, и вывод аргумента шаблона все еще может быть успешным.

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

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