使用typedef的std::vector重载C++中的<<运算符

6

我有一个与获取奇怪的问题。我已经使用std::vector进行了typedef并且它包含一些自己的类:

typedef std::vector<data::WayPoint> TWayPointList;

这是一个嵌套在结构体DataHandler中的类型,它存在于一些命名空间data中。

现在,我想打印出向量的单个内容。为此,我的想法是重载& lt;<运算符并循环遍历typedef的向量的单个元素。因此,我在结构DataHandler中声明了以下输出运算符:

namespace data
{
    structure DataHandler
    {

        // ... some code
        typedef std::vector<data::WayPoint> TWayPointList;

        // ... some more code

        /**
         * @brief Globally overloaded output operator
         *
         * @param[in] arOutputStream Reference to output stream.
         * @param[in] arWayPointList WayPoint which should be printed to output stream.
         */
         LIB_EXPORTS friend std::ostream& operator<<(std::ostream& arOutputStream, const data::DataHandler::TWayPointList& arWayPointList);
    } // structure DataHandler
} // namespace data

并在对应的源文件中定义它:

namespace data
{
   std::ostream& operator<<(std::ostream& arOutputStream, const DataHandler::TWayPointList& arWayPointList)
    {
        for(DataHandler::TWayPointList::const_iterator lIterator = arWayPointList.begin(); lIterator < arWayPointList.end(); ++lIterator)
        {
            arOutputStream << *lIterator << std::endl;
        }

        return arOutputStream;
    }
} // namespace data

这段代码可以正常编译。但是如果我添加了类似于以下的内容:
int main(int argc, char *argv[])
{
    // create Waypoint
    data::WayPoint lWayPoint;

    // create waypoint list
    data::DataHandler::TWayPointList lWayPointList;

    // append two elements
    lWayPointList.push_back(lWayPoint);
    lWayPointList.push_back(lWayPoint);

    std::cout << lWayPointList << std::endl;

    return 0;
}

在我的testmain.cpp文件中,编译器提到无法找到正确的operator<<运算符(并做出了很多假设,包括一些我在其他类中定义的)。出现了类似这样的错误。
src/main.cpp:107: error: no match for 'operator<<' in 'std::cout << lWayPointList'
src/main.cpp:107:18: note: candidates are:
... a long list of canditates...

我认为这与ADL有关,但我并没有理解重点。

那么,您有任何想法和建议让代码正常工作吗?

[编辑] 我已经添加了一些文件到源代码中,并提供错误输出以进行澄清。


3
请复制/粘贴您所提到的实际编译器错误/警告。 - CrazyCasta
1
你是故意混淆 datamkilib 吗? - Kerrek SB
你是否定义了运算符 <<(ostream&, const WayPoint&) - Rudolf Mühlbauer
如果data是一个命名空间,那么你的operator<<定义必须放在该命名空间内部。 - ildjarn
@KerrekSB 嗯,是的 - 这种情况是复制代码到stackoverflow上时发生的 - 很抱歉。我已经纠正了这个问题。 - nightsparc
@RudolfMühlbauer 是的,WayPoint类的<<运算符已经定义好了(而且运行良好...) - nightsparc
2个回答

6

友元声明是在拥有这种友元声明的类的命名空间中在命名空间级别上声明函数。从运算符的定义中可以看出,您正在全局命名空间中定义它(顺便说一句,您在友元声明中的注释非常遗憾,编译器不会读取注释)。您需要在正确的命名空间中定义operator<<

std::ostream& mkilib::operator<<(std::ostream& arOutputStream,
            /*^^^^^^^^*/         const mkilib::DataHandler::TWayPointList& arWayPointList)

或者,另一种选择是:
namespace mkilib {
    std::ostream& operator<<(std::ostream& arOutputStream,
                             const DataHandler::TWayPointList& arWayPointList) {...}
}

在您的程序中,有两个接受TWayPointList对象的operator<<声明,一个在全局命名空间中(定义为自我声明),另一个在::mkilib命名空间中(来自友元声明)。参数依赖查找会找到::mkilib中的那个,但这在代码中从未被定义。
更新后,似乎这不是真正的问题,因为编译器无法找到重载(上面的答案是关于已编译但未链接的代码)。从您的代码到您要求的命名空间,您改变了一些东西。如果Waypoint和接受std::vector<Waypoint>operator<<在同一个命名空间中定义,则ADL将找到正确的重载。请注意,定义DataHandler的命名空间没有任何影响。
实际上,现在我想起来了,原始答案确实适用。友元声明对查找没有影响,因为ADL不会在DataHandler内部搜索该运算符,因此唯一的operator<<声明是在定义中的自我声明。
请注意,友元声明在命名空间级别上声明一个实体,但该声明仅在具有友元声明的类内部可见。
建议:避免使用指令,它们只会带来混乱和痛苦。如果需要,重新打开命名空间或限定标识符...使用指令会使查找的推理变得更加复杂。

嗯,抱歉,我没听懂。你能再解释一下吗?哦,我在上面的代码示例中添加了几行... - nightsparc
@nightsparc:看起来这并不是你的问题,因为你添加的错误信息表明编译器找不到重载,而不是链接器找不到定义。从你的代码到你所问的问题中有一些变化,或者你可能缺少一个#include或类似的东西,因为如果ADL能够在容器中存储的类型相同的命名空间中找到重载,它应该能够找到。我已经更新了答案。 - David Rodríguez - dribeas
1
@nightsparc:再次更新,原始答案仍然适用,尽管原因不同。在DataHandler内部的声明不会被ADL视为operator<<调用中的参数之一,因此operator<<的唯一声明位于全局命名空间中。 - David Rodríguez - dribeas
Rodriguez:非常感谢您详细的回答,我明白了 :-)。问题在于,我的 WayPoint 类和 typedef 后的向量没有在同一个命名空间中(该类位于 data 的某个子命名空间中)。因此,ADL 无法找到正确的重载。我已经改变了这一点。现在,typedef 是与 WayPoint 类定义相同的命名空间的一部分 - 可以找到正确的重载。现在我在思考,将类和向量 typedef 放在同一个命名空间内的耦合似乎比我之前做的拆分更自然... - nightsparc
2
请注意,typedef并不重要。编译器将把typedef解析为实际类型std::vector<ns::Waypoint>,并在ADL期间检查模板及其模板参数的名称空间(即在此情况下的::std::ns)。typedef可以位于任何命名空间中,但只有实际类型的命名空间将被搜索。 - David Rodríguez - dribeas

0

从您的代码片段中我看到您没有使用using namespace data,但是您的运算符被定义在data命名空间中。您可以使用data命名空间,但我相信您有自己的原因。

与其声明一个特定于数据类型的运算符,不如像这样在命名空间之外定义一个通用运算符:

template<typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& list)
{
    for(std::vector<T>::const_iterator iter = list.begin(); iter != list.end(); ++iter)
    {
        out << *iter;
    }
}

一个为所有向量类型重载运算符<<的库肯定是个糟糕的库。 - Burak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接