【问题标题】:argument deduction with template template parameters使用模板模板参数进行参数推导
【发布时间】:2014-02-03 17:55:13
【问题描述】:

我有一个类模板。在这个类模板中,我试图定义一个成员函数模板,它接受const_iterators 集合上的strings。集合本身可以是任何类型的 StdLib 集合,但实际上它将是 vectorlist

由于集合可以是任何类型,我使用template-template 参数来指定集合类型。但是,它将始终是string 的集合。我希望模板参数推导起作用,这样我就不必在调用成员函数时指定集合类型。

SSCCE 中的代码类似于我的预期用例。

到目前为止,我已经为类定义(Live Demo):

template <typename Foo>
struct Gizmo
{
    Foo mF;
    Gizmo (Foo f) : mF (f) {};

    template <template <typename> class Cont> void DoIt(
        typename Cont <string>::const_iterator begin,
        typename Cont <string>::const_iterator end)
        {
            stringstream ss;
            ss << "(" << this->mF << ")\n";
            const std::string s = ss.str();
            copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
        }
};

类模板实例化编译成功:

int main()
{
    list <string> l;
    l.push_back ("Hello");
    l.push_back ("world");

    Gizmo <unsigned> g (42);
}

但是,当我尝试利用论据演绎(没有它,整个练习几乎毫无意义):

g.DoIt (l.begin(), l.end());

GCC 抱怨它无法推断出模板参数:

prog.cpp: In function ‘int main()’:
prog.cpp:34:28: error: no matching function for call to ‘Gizmo<unsigned int>::DoIt(std::list<std::basic_string<char> >::iterator, std::list<std::basic_string<char> >::iterator)’
  g.DoIt (l.begin(), l.end());
                            ^
prog.cpp:34:28: note: candidate is:
prog.cpp:16:49: note: template<template<class> class typedef Cont Cont> void Gizmo<Foo>::DoIt(typename Cont<std::basic_string<char> >::const_iterator, typename Cont<std::basic_string<char> >::const_iterator) [with Cont = Cont; Foo = unsigned int]
  template <template <typename> class Cont> void DoIt(
                                                 ^
prog.cpp:16:49: note:   template argument deduction/substitution failed:
prog.cpp:34:28: note:   couldn't deduce template parameter ‘template<class> class typedef Cont Cont’
  g.DoIt (l.begin(), l.end());

最终我真正关心的是能够在string 的集合上使用开始和结束迭代器调用DoIt。集合的实际类型可以是vectorlist,我既不想指定模板参数,也不想基于容器重载。

我怎样才能让它工作?

请注意,我的实际用例将使用 C++03。欢迎使用 C++11 解决方案,但我只能接受 C++03 解决方案。

【问题讨论】:

  • 这不是非推理上下文的例子吗?
  • @faranwath:啊。嗯。也许是的。我不喜欢使用模板模板参数。你能想出一个替代方案吗?
  • 是否有理由通过容器而不是迭代器进行参数化?
  • @zch:那行不通,因为vector 不是类型。只有vector &lt;string&gt; 是。有意义吗?
  • @MikeSeymour:是的,但可以说这可能不是一个好的理由。我想我可能很难捍卫它。但除此之外,当编译器不服从我的意愿时,我变得很固执。

标签: c++ templates template-templates template-argument-deduction


【解决方案1】:

有几个问题。我为您修复了模板模板参数。我还修改了方法签名,以便您可以自动推断类型,但它需要传入原始集合:

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <sstream>
#include <iterator>
using namespace std;


template <typename Foo>
struct Gizmo
{
    Foo mF;
    Gizmo (Foo f) : mF (f) {};

    template <template <typename T, typename A = allocator<T> > class Cont> void DoIt(
        const Cont <string> &, // deduction
        const typename Cont <string>::iterator &begin,
        const typename Cont <string>::iterator &end)
        {
            stringstream ss;
            ss << "(" << this->mF << ")\n";
            const std::string s = ss.str();
            copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
        }
};

int main()
{
    list <string> l;
    l.push_back ("Hello");
    l.push_back ("world");

    Gizmo <unsigned> g (42);
    g.DoIt (l, l.begin(), l.end());
}

See it run here.

【讨论】:

  • 我认为没有其他方法,除非您愿意通过以下方式调用该方法:'DoIt(it.begin(), it.end)'
【解决方案2】:

我好像没有抓住重点,但你为什么不能这样做呢?

template <typename Iterator> void DoIt(
    Iterator begin,
    Iterator end)
    {
      // snip
    }

// [...]

list <string> l;
l.push_back ("Hello");
l.push_back ("world");
vector <string> v;
v.push_back ("Hello");
v.push_back ("world");

Gizmo <unsigned> g (42);
g.DoIt (l.begin(), l.end());
g.DoIt (v.begin(), v.end());

【讨论】:

  • 我可以。如果重点是我对编译器很固执,那么是的,你错过了重点。实际上我已经在我的真实代码中做到了这一点。我试图做的是定义一个函数,它可以将迭代器加入任何类型的容器,只要容器持有strings。似乎这是不可能的,同时保持可推理性和缺乏黑客攻击。
【解决方案3】:

我认为您实际上并不关心您的输入是vector,还是list,甚至是容器。我认为你真正关心的是你有一系列可以迭代的东西,它们可以转换为string。所以你应该接受任何一对迭代器,其 value_type is_convertiblestring (Live demo at Coliru):

template <typename Iter>
typename enable_if<
  is_convertible<
    typename iterator_traits<Iter>::value_type,string
  >::value
>::type DoIt(Iter begin, Iter end) const
{
    stringstream ss;
    ss << "(" << this->mF << ")\n";
    const std::string s = ss.str();
    copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
}

我为约束的丑陋道歉,Concepts Lite 不能很快到达这里。

【讨论】:

    【解决方案4】:

    问题是对于非常量字符串beginend 函数返回string::iterator,而不是string::const_iterator。你应该写一个类似的函数,只有string::iterator。或者,在 c++11 中,您可以使用 cbegin 和 cend 函数。

    【讨论】:

    • 你是说vector&lt;string&gt; begin 不返回const_iterator?我希望不会,因为那不是真的。
    • 不,不幸的是,在 C++ 中,编译器无法确定您是否只需要 const_iterator 而不是 iterator,因此对于 const vectors 它返回 const_iterator 而对于非const vectors 它返回 iterator。见cplusplus.com/reference/vector/vector/begin
    • 当然有。 const_iterator 重载为 const
    • 请注意,我在 容器 上调用 begin(),而不是 string
    • 只需启动此代码:#include #include using namespace std; int main() { 向量 a;常量向量 b; cout
    【解决方案5】:

    这是一个非推断上下文,您无法解决此问题。

    给定一个迭代器,你不可能知道它属于哪种容器。例如,原始指针可以作为一种以上容器类型的迭代器。

    幸运的是,您从未将容器类型用于任何事情,因此您可以将其丢弃并在迭代器上进行参数化。

    但我确实使用它来确保我的容器包含字符串!

    不,你没有。谁告诉你 Foo&lt;string&gt;::iterator 取消对字符串的引用(或者它存在,就此而言)?当然,如果它存在,它可能取消引用到一个字符串,因为现有的约定,但这绝不是保证。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-30
      • 1970-01-01
      • 1970-01-01
      • 2018-12-03
      • 1970-01-01
      • 1970-01-01
      • 2013-09-14
      • 2021-06-16
      相关资源
      最近更新 更多