【问题标题】:How to implement generic method for STL containers that haven`t common interface needed for that method using template template parameter如何使用模板模板参数为没有通用接口的 STL 容器实现通用方法
【发布时间】:2013-03-24 15:18:29
【问题描述】:

问题陈述(出于教育目的):
-实现方法 printContainer,它适用于 STL 容器 vectorstackqueuedeque

我做了一个解决方案,但由于代码量过多,我不喜欢它。
我做了什么来解决这个问题:
1.设计的通用函数期望容器统一接口进行操作:获取最后一个元素的值并从容器中删除该元素

template <typename T>
void printContainer(T container)
{
    cout << " * * * * * * * * * * " << endl;
    cout << " operator printContainer(T container). Stack, queue, priority queue" 
         << endl;
    cout << typeid(container).name() << endl;

    while (!container.empty())
    {
            cout << top(container) << "    ";
            pop(container);
    }
    cout << endl;
    cout << " * * * * * * * * * * * " << endl;
}

我为每个容器实现了允许提供统一接口的功能 (我想重构下面的代码sn-p):

template <typename T>
typename vector<T>::value_type top(const vector<T>& v)
{
    return v.back();
}
template <typename T, typename Base>
typename stack<T, Base>::value_type top(const stack<T, Base>& s)
{
    return s.top();
}

template <typename T, typename Base>
typename queue<T, Base>::value_type top(const queue<T, Base>& q)
{
    return q.front();
}

template <typename T, typename Base>
typename priority_queue<T, Base>::value_type top(const priority_queue<T, 
                                                              Base>& pq)
{
    return pq.top();
}

template <typename T>
void pop(vector<T>& v)
{
    return v.pop_back();
}

template <typename T, typename Base>
void pop(stack<T, Base>& s)
{
    return s.pop();
}

template <typename T, typename Base>
void pop(queue<T, Base>& q)
{
    return q.pop();
}

template <typename T, typename Base>
void pop(priority_queue<T,Base>& pq)
{
    return pq.pop();
}

我不想用这样的东西替换它:

template <typename T, typename Base, template<typename T, class Base, 
class ALL = std::allocator<T>> class container>
typename container<T,Base>::value_type top(container<T,Base>& c)
{
    if (typeid(container).name == typeid(vector<T,Base>))
        return c.back();
    if (typeid(container).name == typeid(queue<T,Base>))
        return c.front();
    else
        return c.top();
}

template <typename T, typename Base, template<typename T, class Base, 
class ALL = std::allocator<T>> class container>
typename container<T,Base>::value_type pop(container<T,Base>& c)
{
    if (typeid(container).name == typeid(vector<T,Base>))
        c.pop_back();
    else
        return c.pop();
}

但它不起作用,我收到如下错误:

Error   1   error C2784: 'container<T,Base>::value_type top(container<T,Base> &)' : could not deduce template argument for 'container<T,Base> &' from 'std::stack<_Ty>'

问题:
我应该在模板模板参数中进行邻接以排除错误,也许有一些我忽略或存在逻辑错误。
无论如何,欢迎任何有用的信息。
提前致谢!

更新:

//这就是我试图调用函数的方式

int arr[] = {1,2,3,4,5,6,7,8,9,0};
    stack<int> s(deque<int>(arr, arr + sizeof(arr) / sizeof(arr[0])));;
    queue<int> q(deque<int>(arr, arr + sizeof(arr) / sizeof(arr[0])));
    priority_queue<int> pq(arr, arr + sizeof(arr) / sizeof(arr[0]));
    printContainer(s);
    printContainer(q);
    printContainer(pq);

【问题讨论】:

  • 在最后一个例子中,第二个函数模板不应该命名为pop()而不是top()吗?
  • 是的,没错,谢谢 - 应该是 pop()

标签: c++ templates stl c++11


【解决方案1】:

这个解决方案:

template <typename T, typename Base, template<typename T, class Base, 
class ALL = std::allocator<T>> class container>
typename container<T,Base>::value_type top(container<T,Base>& c)
{
    if (typeid(container).name == typeid(vector<T,Base>))
        return c.back();
    if (typeid(container).name == typeid(queue<T,Base>))
        return c.front();
    else
        return c.top();
}

不会工作,因为if() 实现了一个run-time 选择,这意味着所有分支的代码都必须编译,即使只有其中一个的计算结果为true,并且并非所有容器都提供函数top()(例如vector)。

考虑这个更简单的例子来解释:

struct X { void foo() { } };
struct Y { void bar() { } };

template<bool b, typename T>
void f(T t)
{
    if (b)
    {
        t.foo();
    }
    else
    {
        t.bar();
    }
}

int main()
{
    X x;
    f<true>(x); // ERROR! bar() is not a member function of X 

    Y y;
    f<false>(y); // ERROR! foo() is not a member function of Y
}

在这里,我将一个在编译时已知的布尔模板参数传递给函数f()。如果输入类型为X,我将传递true,因此支持名为foo() 的成员函数;如果输入类型为Y,我将传递false,因此支持名为bar() 的成员函数。

即使选择在编译时已知的布尔值上起作用,语句本身也会在运行时执行。编译器首先必须编译整个函数,包括if 语句的false 分支。

您正在寻找的是某种static if construct,遗憾的是它在 C++ 中不可用。

这里的传统解决方案是基于重载的,实际上看起来就像您最初提供的那样。

【讨论】:

  • @spin_eight:是的,我注意到了。但这并不重要,问题比这更深(请参阅答案以获得澄清)
  • 我现在正在浏览它,我需要一些时间来理解那里提到的想法。
  • “这意味着所有分支的代码都必须编译” - 我认为它们是在模板实例化时编译的。 “并且并非所有容器(例如vector)都提供函数top()” - 这就是我实现top(vector)的原因。抱歉,我不会将t get from you answer why idea with template template argument cant 应用于我的任务
  • @spin_eight:确实,它们是在模板实例化时编译的,但您的 if 语句的所有分支都已编译。这就是我的回答中的示例试图澄清的内容。你不能说“如果它是一个向量调用这个函数,否则调用这个另一个函数”,因为当你传递一个向量时,编译器将不得不编译“如果它是一个向量”分支和“否则”分支,两者之一不会编译。
  • 感谢您清晰而良好的解释,我明白了。所以要检查我是否做对了:当我调用向量模板的函数时,应该实例化,因此编译器应该为该模板内使用的所有对象和函数生成代码,但是因为没有方法 top() & front( ) 无法生成它们的矢量代码,这是主要问题。
【解决方案2】:

我会反其道而行之。我会编写一个使用迭代器的通用函数:

template <class Iter>
void show_contents(Iter first, Iter last) {
    // whatever
}

然后是一个接受容器的通用函数:

template <class Container>
void show_container(const Container& c) {
    show_contents(c.begin(), c.end());
}

然后是获取queuestack 下的容器的技巧:

template <class C>
struct hack : public C {
    hack(const C& cc) : C(cc) { }
    typename C::Container::const_iterator begin() const {
        return this->c.begin();
    }

    typename C::Container::const_iterator end() const {
        return this->c.end();
    }
};

然后定义特化来创建这些对象并显示它们的内容:

template <class T>
void show_container(const stack<T>& s) {
    hack<stack<T>> hack(s);
    show_contents(hack.begin(), hack.end());
}

template <class T>
void show_container(const queue<T>& q) {
    hack<stack<T>> hack(q);
    show_contents(hack.begin(), hack.end());
}

【讨论】:

  • 谢谢,我非常喜欢这个想法(使用底层容器并使用它们提供的迭代器)。最初我打算使用底层容器,而不是适配器,因为这将只允许处理 3 个容器(列表、向量、双端队列)。如果我决定使用适配器,将会有更多工作要做。但我拒绝了我的想法,因为我没有t know how to get iterators for a underlying container. Now I am trying to implement you variant, but I cant 使 struct hack 工作 - 我收到错误:错误 1 ​​错误 C2039: 'const_iterator' : is not a member of 'std::stack<_ty>'
  • @spin_eight - 抱歉。应该是typename C::Container::const_iterator。固定。
  • @"Pete Becker" 是的,这很有帮助,感谢您的好主意和正确的实施。
  • 它仅适用于基本容器,不适用于适配器!
【解决方案3】:

虽然 Andy 的回答已经很好,但我想对您的实施添加一项改进。您可以对其进行改进以支持更多容器专业化,因为您的重载不允许 STL 容器必须为非默认的所有模板参数。例如,看看你的代码:

template <typename T>
typename vector<T>::value_type top(const vector<T>& v)
{
    return v.back();
}

现在将它与std::vector 的定义进行比较。类模板有两个参数,即std::vector&lt;T,Allocator=std::allocator&lt;T&gt;&gt;。你重载只接受那些std::vectors,其中第二个参数是std::allocator&lt;T&gt;

虽然您可以手动向代码中添加更多参数,但还有一个更好的选择:可变参数模板。您可以将以下代码用于所有std::vectors 的真正通用版本:

template <typename... Ts>
typename vector<Ts...>::value_type top(const vector<Ts...>& v)
{
    return v.back();
}

当然,您可以对所有其他容器使用相同的技术,而不必担心它们具有的模板参数的确切数量。有些容器甚至有高达five template parameters,所以如果你不使用可变参数模板,这可能会很烦人。

一个警告:一些较旧的编译器可能不喜欢可变参数版本,您必须手动迭代所有参数。

【讨论】:

  • 感谢您提供信息。 “因为您的重载不允许 STL 容器必须为非默认的所有模板参数” - 我认为一个模板参数足以使我的模板函数适用于所有基本容器(列表、向量、双端队列)因为分配器的第二个参数 ist used in my function at all that means that my function doesnt 需要从模板中推导出来。另外为了检查我的猜测,我实现了分配器(josuttis.com/libbook/memory/myalloc.hpp.html),并且我的函数例如用于带有该分配器的向量
  • 我的意思是省略第二个模板参数,即分配器不会t limits the scope of my function usage, this isnt 这种情况,尽管还有其他因素限制它。
  • @spin_eight:std::vector&lt;T&gt; 的重载将与 std::vector&lt;T,MyAlloc&lt;T&gt;&gt; 不匹配 - 如果匹配,这是一个严重的编译器错误。至少,这就是我阅读您的 cmets 的方式。如果你只是说你不需要它,好吧。
  • 是的,它匹配,如果它是一个错误,那么这种行为应该与 C++ 标准相矛盾,请您指出矛盾之处。
  • @"Daniel Frey" 在对我的代码进行了一些实验之后,我得出的结论是你是对的。感谢您指出省略的 2-nd 模板参数的问题,现在我了解它是如何工作的以及可能出现的问题。
猜你喜欢
  • 2011-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多