Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using std::is_same, why my function still can't work for 2 types

I am trying to write a function that can print both stack and queue, my code is as following

template<typename Cont>
void print_container(Cont& cont){
    while(!cont.empty()){
        if(std::is_same<Cont, stack<int>>::value){
            auto elem = cont.top();
            std::cout << elem << '\n';
        } else {
            auto elem = cont.front();
            std::cout << elem << '\n';
        }
        cont.pop();
        std::cout << elem << '\n';
    }
}

int main(int argc, char *argv[])
{
    stack<int> stk;
    stk.push(1);
    stk.push(2);
    stk.push(3);
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);

    std::cout << "print stack" << endl;
    print_container(stk);
    std::cout << "print queue" << endl;
    print_container(q);

    return 0;
}

But it doesn't work here, the error info is:

demo_typeof.cpp:35:30: error: no member named 'front' in 'std::__1::stack<int, std::__1::deque<int, std::__1::allocator<int> > >'
            auto elem = cont.front();
                        ~~~~ ^
demo_typeof.cpp:52:5: note: in instantiation of function template specialization 'print_container<std::__1::stack<int, std::__1::deque<int, std::__1::allocator<int> > > >' requested here
    print_container(stk);
    ^
demo_typeof.cpp:32:30: error: no member named 'top' in 'std::__1::queue<int, std::__1::deque<int, std::__1::allocator<int> > >'
            auto elem = cont.top();
                        ~~~~ ^
demo_typeof.cpp:54:5: note: in instantiation of function template specialization 'print_container<std::__1::queue<int, std::__1::deque<int, std::__1::allocator<int> > > >' requested here
    print_container(q);
    ^
2 errors generated.

I know it's problematic, and know C++ is statically typed and without too much Runtime support. but I am wondering the specific reason why this doesn't work, and how to deal with it.

P.S.: The actual meaning of judge the typing of a container is that: You can simply change a DFS function into BFS by passing a queue container instead of a stack. So, BFS and DFS can share most of the code.

P.P.S: I am in C++ 11 environment, but answers for older or later standard are also welcomed.

like image 219
pambda Avatar asked May 09 '18 12:05

pambda


4 Answers

Both branches of if-else statement must be compilable, which are not in your case. One of many possible solutions that is based on partial specialization and should work even in C++98:

template <typename Cont>
struct element_accessor;

template <typename T>
struct element_accessor<std::stack<T>> {
   const T& operator()(const std::stack<T>& s) const { return s.top(); }
};

template <typename T>
struct element_accessor<std::queue<T>> {
   const T& operator()(const std::queue<T>& q) const { return q.front(); }
};

template<typename Cont>
void print_container(Cont& cont){
   while(!cont.empty()){
      auto elem = element_accessor<Cont>{}(cont);
      std::cout << elem << '\n';
      cont.pop();
   }
}

A C++17 solution with if constexpr:

template<template<class> typename Cont, typename T>
void print_container(Cont<T>& cont){
   while(!cont.empty()){
      if constexpr (std::is_same_v<Cont<T>, std::stack<T>>) 
         std::cout << cont.top() << '\n';
      else if constexpr (std::is_same_v<Cont<T>, std::queue<T>>) 
         std::cout << cont.front() << '\n';
      cont.pop();
   }
}
like image 70
Daniel Langr Avatar answered Oct 21 '22 18:10

Daniel Langr


I answered my own question by simply using overloads.

template<typename Elem>
Elem get_first_elem(stack<Elem>& cont){
    return cont.top();
}
template<typename Elem>
Elem get_first_elem(queue<Elem>& cont){
    return cont.front();
}

template<typename Cont>
void print_container(Cont& cont){
    while(!cont.empty()){
        auto elem = get_first_elem(cont);
        cont.pop();
        std::cout << elem << '\n';
    }
}
like image 35
pambda Avatar answered Oct 21 '22 18:10

pambda


The problem with

if(std::is_same<Cont, stack<int>>::value)
    ...
else
    +++

Is that if ... or +++ would not compile then you can't use it. Even though you can only take one branch or the other both branches get compiled and if the syntax isn't valid then you get a compiler error. With C++17's if constexpr though the behavior is different. The condition will be resolved at compile time and only the branch that is taken will actually be compiled. The rest of the code is discarded. Switching to that you code would look like

template<typename Cont>
void print_container(Cont& cont){
    while(!cont.empty()){
        if constexpr(std::is_same<Cont, stack<int>>::value){
            auto elem = cont.top();
            std::cout << elem << '\n';
        } else {
            auto elem = cont.front();
            std::cout << elem << '\n';
        }
        cont.pop();
        std::cout << elem << '\n';
    }
}
like image 4
NathanOliver Avatar answered Oct 21 '22 18:10

NathanOliver


If you can use C++17 then what you want is if constexpr

#include <stack>
#include <queue>
#include <iostream>

using namespace std;

template<typename Cont>
void print_container(Cont& cont){
    while(!cont.empty()){
        if constexpr(std::is_same<Cont, stack<int>>::value){
            auto elem = cont.top();
            std::cout << elem << '\n';
        } else {
            auto elem = cont.front();
            std::cout << elem << '\n';
        }
        cont.pop();
    }
}

int main(int argc, char *argv[])
{
    stack<int> stk;
    stk.push(1);
    stk.push(2);
    stk.push(3);
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);

    std::cout << "print stack" << endl;
    print_container(stk);
    std::cout << "print queue" << endl;
    print_container(q);

    return 0;
}
like image 3
Mateusz Drost Avatar answered Oct 21 '22 19:10

Mateusz Drost