【问题标题】:std::ostream& operator<< type deductionstd::ostream& operator<< 类型推演
【发布时间】:2013-07-28 07:44:22
【问题描述】:

我有一个这样的模板函数

#include <list>
#include <iostream>

template<typename T>
std::ostream& operator<<(std::ostream& out, const std::list<T>& list){
    out << "[";
    if(!list.empty()){
        typename std::list<T>::const_iterator it = list.cbegin();
        out << *it;
        for (++it; it != list.cend(); ++it){
            out << ", ";
            out << *it;
        }
    }
    out << "]";
    return out;
}

还有一些带有嵌套类的模板类

namespace my{

    template<
        typename T,
        typename U = size_t
    >

    class graph{

    public:
        typedef T dist_t;
        typedef U node_t;

        class node_pt;
        typedef struct arc_t{
            node_pt* from = nullptr;
            node_pt* to = nullptr;
            dist_t weight;
        } arc_t;
        typedef struct arc_pt{
            arc_t arc;
        } arc_pt;
        typedef struct node_pt{
            node_t node;
        } node_pt;

        class arc_iterator{
        public:
            arc_pt* pt = nullptr;
        public:
            arc_pt* operator->() const{
                return pt;
            }

            friend std::ostream& operator<< (std::ostream &out, const arc_iterator& it) {
                out << "(" << it->arc.from->node << "," << it->arc.to->node << "," << it->arc.weight << ")";
                return out;
            }
        };

        class node_iterator{
        public:
            node_pt* pt = nullptr;

        public:

            node_t operator *() const{
                return pt->node;
            }

            friend std::ostream& operator<< (std::ostream &out, const node_iterator& it) {
                out << *it;
                return out;
            }
        };

    };
}

重现问题的一些代码

namespace my{
    namespace test{
        void run(){     
            typedef my::graph<size_t> graph_t;
            std::list<graph_t::node_t> l1;
            std::list<graph_t::dist_t> l2;
            std::list<graph_t::node_iterator> l3;
            std::list<graph_t::arc_iterator> l4;

            std::cout << l1 << std::endl;
            std::cout << l2 << std::endl;
            std::cout << l3 << std::endl;
            std::cout << l4 << std::endl;
        }
    }
}


int main(){
    my::test::run();
}

问题是如果我定义了两个友元方法,它就无法编译。如果我只定义一种方法并注释其中一个迭代器列表打印它可以工作。

我得到的错误是

src/OTest_Graph.cpp: In member function ‘virtual void my::test::TestGraph::run()’:
src/OTest_Graph.cpp:59:53: error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’
In file included from /usr/include/c++/4.7/iostream:40:0,
                 from h/OTest_Graph.h:4,
                 from src/OTest_Graph.cpp:1:
/usr/include/c++/4.7/ostream:600:5: error:   initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = std::list<my::graph<long unsigned int>::node_iterator, std::allocator<my::graph<long unsigned int>::node_iterator> >]’

谁能告诉我这里发生了什么?

【问题讨论】:

    标签: c++ templates cout nested-class type-deduction


    【解决方案1】:

    好吧,代码在 clang++ 中为我编译和运行。无法在这台计算机上使用 g++。

    编辑: 实际上,它也可以使用 g++ 编译,这是有道理的,因为您只在全局命名空间中的 main 中使用 operator&lt;&lt;。我假设您的实际代码不同 \Edit

    但我熟悉“ostream lvalue can't bind to ostream&&”错误

    如何解释。在ostreams 和任何std 类之间提供operator&lt;&lt; 存在问题(例如您的示例中的list,但我发现它是vector

    大多数情况下它可以工作,但是当从命名空间(如您的 my 命名空间)调用运算符时,它会中断。

    为什么?因为“我在哪里可以找到这个操作符

    它在每个操作数的名称空间中查找(在您的情况下 - 两者都来自 std)。 有时在调用者的命名空间中(在你的例子中是my)。

    我说“有时”是因为根据标准它不应该这样做,但 g++ 无论如何都会这样做。 clang++ 没有——而是在全局命名空间中查找(因此它对我有用)

    理想情况下,您希望将 operatorstd 命名空间内(试试看 - 它会起作用)。但是 - 这是违反标准的。你不能这样做。你可以把它放在 my 命名空间中,它应该可以在 g++ 中找到,但在其他编译器中则不行。

    这是个问题。我通过创建一个包装器“解决”了它 - 一个存在于我自己的命名空间中并且只包含对 std 类的引用的类 - 并且可以打印。

    template<class T> struct OutList<T>{
      const std::list<T> &lst;
      OutList(const std::list &l):lst(l){}
    };
    
    template<class T> OutList<T> outlist(const std::list<T> &lst){return OutList<T>(lst);}
    
    std::ostream &operator<<(std::stream &out,const OutList<T> &lst){...}
    
    ....
    std::cout << "list= "<<outlist(list)<<std::endl;
    

    它不漂亮,但这就是我发现的全部......

    【讨论】:

    • 其实我的 main 代码在 my::test 命名空间中,如果我写 ::operator&lt;&lt;(std::cout, l1); 它编译...
    • 你会建议我在未来的 c++ 项目中使用 clang 而不是 g++ 吗?
    • 不,我建议您编写可在所有编译器上编译的代码。可能不那么漂亮,但编写可在所有内容上编译的代码非常重要。
    • 好的,但是使用 clang 代替 g++ 有什么好处吗?
    • 是的!可读性极强的错误消息。我不是在开玩笑,试试看。但是优化器少了一点……不错
    【解决方案2】:

    我在全局命名空间中声明的以下运算符遇到了同样的问题:

    template <typename T>
    std::ostream & operator << (std::ostream &os, const std::vector<T> &vector);
    

    …当从命名空间中声明的函数调用时:

    std::ostream & operator << (std::ostream &os, const Foo &foo) {
        return os << foo.items;  // error
    }
    

    …其中Foo::itemsstd::vector

    g++ 给出了臭名昭著的错误:

    error: cannot bind 'std::basic_ostream<char>' lvalue to 'std::basic_ostream<char>&&'
    

    出现错误是因为 C++11 引入了一个包罗万象的 std::operator &lt;&lt; 模板,&lt;ostream&gt; 中的注释将其描述为“右值流的通用插入器”。编译器找不到全局 ::operator &lt;&lt; 模板,因为参数相关查找首先找到 std::operator &lt;&lt; 模板。

    一个简单而正确的解决方法是通过using 声明将全局运算符带入本地范围:

    std::ostream & operator << (std::ostream &os, const Foo &foo) {
        using ::operator <<;
        return os << foo.items;  // OK
    }
    

    【讨论】:

    • 所以是标准库导致了这个错误,而不是编译器。我将编辑我的答案。
    【解决方案3】:

    错误取决于标准库的版本,见@matt-whitlock's answer

    g++ 4.7 的解决方案:

    代替

    std::cout << l1 << std::endl;
    std::cout << l2 << std::endl;
    std::cout << l3 << std::endl;
    std::cout << l4 << std::endl;
    

    使用

    ::operator<<(std::cout, l1) << std::endl;
    ::operator<<(std::cout, l2) << std::endl;
    ::operator<<(std::cout, l3) << std::endl;
    ::operator<<(std::cout, l4) << std::endl;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-04
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多