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

核能气质少年 提交于 2019-12-21 21:48:44

问题


Problem statement (for an educational purpose):
-Implement method printContainer which works for STL containers vector, stack, queue and deque.

I made a solution, but I don`t like it due to excessive amount of code.
What I did to solve the problem:
1. Designed generic function which expects uniform interface from containers for operations: get value of last element and erase that element from the container

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

For each container I implemented functions that allows to provide uniform interface (I want to refactor the following code snippet):

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

I wan`t to replace it with something like this:

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

but it doesn`t work, I get errors like :

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

Question:
that adjacements should I made in template template paramter to sort out errors, maybe there is something that I overlooked or exist logical errors.
Any way, any usefull information is welcomed.
Thanks in advance!

UPDATE:

// that is how I am trying to invoke the function

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

回答1:


This solution:

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

Won't work, because if() realizes a run-time selection, which means that the code of all branches must compile, even though exactly only one of them evaluates to true, and function top() is not provided by all containers (e.g. vector).

Consider this simpler example for an explanation:

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
}

Here, I am passing a boolean template argument, which is known at compile-time, to function f(). I am passing true if the input is of type X, and therefore supports a member function called foo(); and I am passing false if the input is of type Y, and therefore supports a member function called bar().

Even though the selection works on a boolean value which is known at compile-time, the statement itself is executed at run-time. The compiler will first have to compile the whole function, including the false branch of the if statement.

What you are looking for is some kind of static if construct, which is unfortunately not available in C++.

The traditional solution here is based on overloading, and looks in fact like the one you provided originally.




回答2:


I'd come at it the other way around. I'd write a generic function that uses iterators:

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

then a generic function that takes containers:

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

then a hack to get at the container that underlies a queue or a stack:

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

then define specializations to create these objects and show their contents:

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:


While Andy's answer is already a good one, I'd like to add one improvement about your implementation. You could improve it to support more container specializations, as your overloads don't allow all the template parameters that the STL containers have to be non-default. For example, look at your code:

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

and now compare it with the definition of std::vector. The class template has two parameters, namely std::vector<T,Allocator=std::allocator<T>>. You overload only accepts those std::vectors where the second parameter is std::allocator<T>.

While you could manually add more parameters to your code, there is a better alternative: Variadic templates. You can use the following code for a truly generic version for all std::vectors:

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

and, of course, you can use the same technique for all other containers and don't need to worry about the exact number of template parameters they have. Some containers even have up to five template parameters, so this can be quite annoying if you don't use variadic templates.

One caveat: Some older compilers might not like the variadic version, you'll have to manually iterate all parameters.



来源:https://stackoverflow.com/questions/15600198/how-to-implement-generic-method-for-stl-containers-that-havent-common-interface

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!