【问题标题】:C++ template template argument types deductionC++模板模板参数类型推导
【发布时间】:2020-07-09 22:28:33
【问题描述】:

我有代码可以在遍历字符串容器时查找并打印出模式匹配项。在模板化的函数 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.2listdc++11clang++libc++ 都无法编译它。

【问题讨论】:

标签: c++ c++11 templates language-lawyer template-templates


【解决方案1】:

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

模板模板参数std::vector有两个模板参数(第二个有默认参数std::allocator&lt;T&gt;),但是模板模板参数Container只有一个。从 C++17 (CWG 150) 开始,template template argument 允许使用默认模板参数来匹配具有较少模板参数的模板模板参数。

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)

或申请parameter pack

template<typename Iterator, template<typename...> class Container>
void foo(Iterator first, Container<std::pair<Iterator, Iterator>> const &findings)

【讨论】:

    【解决方案2】:

    在某些 C++ 版本中,Container 无法匹配 std::vector,因为 std::vector 实际上不是 template &lt;typename&gt; class。这是一个template &lt;typename, typename&gt; class,其中第二个参数(分配器类型)有一个默认模板参数。

    虽然添加另一个模板参数typename Alloc 使函数参数Container&lt;std::pair&lt;Iterator, Iterator&gt;, Alloc&gt; 可以工作,但这可能是其他容器类型的问题。

    但是由于您的函数实际上并没有使用模板模板参数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)。这对于特定用途的功能通常并不重要,但至少对于通用功能来说,更具限制性(或“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 的解决方案将做到这一点。无论如何,非常感谢你
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2011-10-07
    • 2018-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多