C++ error: invalid use of ‘::’ for `std::cout`

C++ error: invalid use of ‘::’ for `std::cout`

这可能是我最近几年看到的最奇怪的事情了。

我有一个项目可以在两台完全不同的机器(openSUSE Tumbleweed 和 ubuntu 14.04)上完美构建。 我从一台使用 kubuntu 16.04 的新机器开始,这个错误开始发生:

$ g++ -std=c++14 cout_qualif.cpp -lpng -o cout_qualif
In file included from cout_qualif.cpp:1:0:
debug_utils.h:19:19: error: invalid use of ‘::’
 # define msg std::cout

Clang 也指出了一个错误,但消息完全不同:

$ clang -std=c++14 cout_qualif.cpp -lpng -o cout_qualif
In file included from cout_qualif.cpp:3:
In file included from /usr/include/png++/png.hpp:34:
In file included from /usr/include/png.h:317:
/usr/include/zlib.h:94:19: error: non-friend class member 'cout' cannot have a qualified name
    z_const char *msg;  /* last error message, NULL if no error */
                  ^~~
./debug_utils.h:19:19: note: expanded from macro 'msg'
#       define  msg     std::cout
                        ~~~~~^
1 error generated.

我遇到的最简单的测试代码是:

#include <iostream>
#include "debug_utils.h"
#include <png++/png.hpp>

int main()
{
    msg << "Start" << std::endl;

    png::image< png::rgb_pixel > image("input.png");
    image.write("output.png");


    msg << "Finish" << std::endl;

    return 0;
}

和"debug_utils.h":

#ifndef DEBUG_UTILS_H
#define DEBUG_UTILS_H

#include <iostream>

#   define  msg std::cout

#endif // DEBUG_UTILS_H

原来"png.h"包括"zlib.h"并且定义了一个结构:

typedef struct z_stream_s {
// ...
   z_const char *msg;  /* last error message, NULL if no error */

这个 msg 成员是触发错误的原因。如果我将我的 #include "debug_utils.h" 移动到下面的一行,在 #include <png++/png.hpp> 之后,一切似乎都正常。


现在终于有问题了: 为什么这台机器不能编译我的代码,而另外两台可以?


附加信息:

Kubuntu 16.04:
$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609

$ clang --version
clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)

openSUSE Tumbleweed:
g++ is 7.1.1

Ubuntu 14.04:
Exact version not available at hand but I believe it is 4.9.x

回顾一下问题的原因

#   define  msg std::cout
在包含 debug_utils.h 之后,

in debug_utils.h 将 std::cout 替换为整个代码中 msg 的任何实例。由于 msg 是一个常见的短标识符,特别是对于消息缓冲区,意外替换一直是代码中潜伏的风险。解决办法很明显:不要那样做。使用更长的、不太可能被重复的替换,或者根本不这样做并替换宏。在没有看到您的用例的情况下,我可能会用 returns 正确的流和编译器可以轻松内联的函数替换宏。

困惑和问题源于为什么仅在三台候选 PC 中的一台上编译时,宏替换的错误仅由一段简单的测试代码引起。

答案是工具链和支持库的差异。出于某种原因,在这些 PC 中只有一台使用 msg 标识符的 third-party header 包含在测试包含的 third-party header 中程序。另外两个遵循不同的包含路径来构建相同的程序并避免绊倒不需要的替换。