Вирахування типів аргументів шаблону 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.

Вихід Кланг :

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 ++ не можуть зібрати це.


1
Він працює на GCC з -std=c++17та на Clang із -std=c++17-frelaxed-template-template-argsпрапором. Інакше здається, що вам потрібен інший параметр шаблону для розподільника.
HolyBlackCat

@HolyBlackCat, справді, дякую
dannftk

Відповіді:


10

Ваш код повинен працювати нормально, оскільки 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

У деяких версіях C ++ Containerне може збігатися std::vector, оскільки std::vectorнасправді це не a template <typename> class. Це template <typename, typename> classде другий параметр (тип розподільника) має аргумент шаблону за замовчуванням.

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

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

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). Це часто не важливо для функції конкретного призначення, але приємно бути більш обмежувальним (або "SFINAE-friendly") принаймні для функцій загального призначення. Ми можемо додати базове обмеження чимось на кшталт:

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

Насправді я завжди хочу переконатися, що контейнер, наданий у параметрах, передає значення як std :: пара ітераторів, які мають тип першого параметра, тому перше спрощення запропонованої вами функції шаблону не може відповідати моїм вимогам, навпаки до цього друге ваше рішення з SFINAE зробить. У будь-якому випадку, велике спасибі
dannftk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.