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.

lang的输出:

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.2listdc ++ 11也不铛++的libc ++能够编译此。


1
它适用于-std=c++17带有-std=c++17-frelaxed-template-template-args标志的GCC 和带有标志的Clang 。否则,看来您需要分配器的另一个模板参数。
HolyBlackCat

@HolyBlackCat,的确如此,谢谢您
dannftk

Answers:


10

从C ++ 17开始,您的代码应该可以正常工作。(它使用gcc10编译。)

template template参数std::vector有两个模板参数(第二个有default参数std::allocator<T>),但template模板参数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之前,您可以使用模板模板参数的默认参数定义第二个模板参数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实际上不是template <typename> class。这是template <typename, typename> class第二个参数(分配器类型)具有默认模板参数的位置。

尽管添加另一个模板参数typename Alloc可以使function参数起作用Container<std::pair<Iterator, Iterator>, Alloc>,但这对于其他容器类型可能是一个问题。

但是,由于您的函数实际上并未使用template template参数Container,因此无需进行如此复杂的template参数推导,就没有推论出template template参数的所有陷阱和限制:

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

实际上,我总是想确保参数中提供的容器将std :: pair的值传递为具有第一个参数类型的迭代器,因此相反,您提供的模板函数的第一个简化似乎无法满足我的要求,相反到此为止,您使用SFINAE的解决方案就能做到。无论如何,非常感谢
dannftk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.