如何使用模板模板参数实现没有通用接口的STL容器的通用方法

How to implement generic method for STL containers that haven`t common interface needed for that method using template template parameter

本文关键字:STL 方法 接口 何使用 参数 实现      更新时间:2023-10-16

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

我做了一个解决方案,但我不喜欢它,因为代码太多了。
我是怎么解决这个问题的:
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;
}

对于每个容器,我实现了允许提供统一接口的函数(我想重构下面的代码片段):

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

解决方案:

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()实现了一个运行时选择,这意味着所有分支的代码都必须编译,即使其中只有一个计算结果为true,并且并不是所有容器(例如vector)都提供top()函数。

考虑下面这个更简单的例子作为解释:

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结构,不幸的是在c++中不可用。

这里的传统解决方案基于重载,实际上与您最初提供的解决方案类似。

如果是我,我会用另一种方式来处理。我会写一个使用迭代器的泛型函数:

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底层容器的hack:

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());
}

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

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

,现在与std::vector的定义进行比较。类模板有两个参数,即std::vector<T,Allocator=std::allocator<T>>。重载只接受第二个形参为std::allocator<T>std::vector

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

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

,当然,您可以对所有其他容器使用相同的技术,并且不需要担心它们拥有的模板参数的确切数量。有些容器甚至有多达5个模板参数,所以如果你不使用可变模板,这可能会很烦人。

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