【问题标题】:using std::is_same, why my function still can't work for 2 types使用 std::is_same,为什么我的函数仍然不能用于 2 种类型
【发布时间】:2018-10-19 13:36:06
【问题描述】:

我正在尝试编写一个可以打印堆栈和队列的函数,我的代码如下

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

但是这里不行,错误信息是:

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.

我知道这是有问题的,并且知道 C++ 是静态类型的并且没有太多运行时支持。但我想知道这不起作用的具体原因,以及如何处理它。

P.S.:判断容器类型的实际含义是:你可以通过传递队列容器而不是堆栈来简单地将DFS函数更改为BFS。所以,BFS 和 DFS 可以共享大部分代码。

P.P.S:我在 C++ 11 环境中,但也欢迎对旧标准或更高标准的答案。

【问题讨论】:

  • 就目前而言,无论类型如何,编译器都会尝试编译if 语句的两个分支,但其中一个总是会失败。
  • 因为if-else 语句的两个分支都必须是可编译的,而您的情况并非如此。您可以使用 C++17 中的if constexpr,或者基于 SFINAE、部分专业化等的一些解决方案。

标签: c++ templates types


【解决方案1】:

我通过简单地使用重载回答了我自己的问题。

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

【讨论】:

  • 这取决于你对“更好的方式”的定义是什么 :)
  • @DanielLangr 这更像是更规范和通用,而不仅仅是一个肮脏的黑客:-)
  • 在我看来这是最好的方法。 ADL 允许我们以统一的方式(如您所做的那样)表达意图,即使该意图如何实现的细节因不同的容器/数据类型而异。
  • @RichardHodges 同意。对于通用解决方案,我将通过 (const) 引用返回,因为这是 stack&lt;T&gt;::topqueue&lt;T&gt;::front 所做的。或者,如果 C++14 可用,最好使用 decltype(auto) 作为返回类型。
【解决方案2】:

if-else 语句的两个分支都必须是可编译的,你的情况不是这样。许多可能的解决方案之一,基于部分专业化,甚至在 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();
   }
}

if constexpr 的 C++17 解决方案:

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

【讨论】:

  • 哦,这是 C++ 优雅 :thumbs_up:
  • 你不需要结构体也不需要部分特化,你只需创建一个重载函数:template &lt;typename T&gt; auto &amp;element_accessor(const std::stack&lt;T&gt; &amp;s) {return s.top();}queue 也是如此。
  • @HolyBlackCat 当然,但这个解决方案已经出现在其他答案中。而 OP,出于某种原因,更喜欢专业化。
  • @YanTing_ThePanda 你认为这比你的解决方案更优雅吗?
  • @YanTing_ThePanda 至少在我看来你的解决方案更好。它本质上与这个解决方案做同样的事情,但代码更少。此外,我认为在这种情况下没有任何理由使用 SFINAE,但如果您需要它,将它与函数重载一起使用会比与类模板特化一起使用更容易。
【解决方案3】:

如果你可以使用 C++17 那么你想要的是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;
}

【讨论】:

    【解决方案4】:

    问题

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

    如果...+++ 不能编译,那么你就不能使用它。即使您只能采用一个分支或其他两个分支都被编译,并且如果语法无效,那么您会收到编译器错误。使用 C++17 的 if constexpr 尽管行为不同。该条件将在编译时解决,并且仅实际编译所采用的分支。其余代码被丢弃。切换到你的代码看起来像

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

    【讨论】:

      猜你喜欢
      • 2018-11-25
      • 1970-01-01
      • 1970-01-01
      • 2021-07-18
      • 2019-04-26
      • 1970-01-01
      • 2018-11-04
      • 1970-01-01
      相关资源
      最近更新 更多