【问题标题】:Not ambiguous identifier没有歧义的标识符
【发布时间】:2018-03-21 03:18:32
【问题描述】:

Visual C++ 2017 干净编译如下,调用用户自定义的log:

// Source encoding: UTF-8 with BOM ∩
#include <algorithm>    // std::for_each
#include <iostream>
#include <math.h>       // ::(sin, cos, atan, ..., log)
#include <string>       // std::string

void log( std::string const& message )
{
    std::clog << "-- log entry: " << message << std::endl;
}

auto main()
    -> int
{
    auto const messages = { "Blah blah...", "Duh!", "Oki doki" };
    std::for_each( messages.begin(), messages.end(), log );         // C++03 style.
}

我认为这是一个编译器错误,因为我设计代码是为了显示标识符如何由于与标准库的名称冲突而产生歧义。

这是编译器的错误吗?


补充信息:MinGW g++ 7.2 发出几条错误消息。它们并不完全提供信息,有 15 行抱怨 std::for_each,但显然它们是由于名称冲突造成的。更改log 的名称,代码编译得很好。


更新:进一步检查表明这显然是一个编译器错误,因为 Visual C++ 编译以下内容(除非定义了符号 D):

#include <cmath>        // std::(sin, cos, atan, ..., log)
#include <string>       // std::string

namespace my{ void log( std::string const& ) {} }
using std::log;
using my::log;

auto main()
    -> int
#ifdef D
{ return !!log; }
#else
{ auto f = log; return f==my::log; }
#endif

Reported to Microsoft(新的 MS 错误报告方案非常错误:它认为将代码自动换行是个好主意,然后拒绝让我上传源代码文件,除非我给它一个“.txt”文件名扩展名)。

【问题讨论】:

  • 我不确定为什么 Visual C++ 编译它。它如何区分您的 log 符号和来自 math.h 的符号?
  • g++ 7.3 在 linux 上给出了一个足够合理的错误“未解决的重载函数类型”。奇怪的是 MinGW 的行为不同。
  • @Galik:它确实在std::for_each 的呈现函数签名中提到了“未解决”。我怀疑 linux 上的所有错误都与 std::for_each 有关,而不是关于 log 的一条预先消息?
  • math.h 中的所有内容不是都可以实现为宏吗?如果log 是一个调用别的东西的宏,那就没有歧义了。
  • @ArneVogel:演示可能相当于一篇更长的论文。但首先是关于实现合规性的部分,它将可诊断规则定义为所有句法和语义规则,除非明确标记为其他规则。然后它继续指出违反可诊断规则需要至少一条诊断消息。这减少了在标准中查找不允许模糊名称引用的特定点的问题。您可以确定它是,否则我们手头的标准存在缺陷。

标签: c++


【解决方案1】:

这是一个编译器错误,因为编译器不应该能够为 for_each 调用执行模板参数推导。

for_each 唯一可以匹配的声明被定义为[alg.foreach]

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function f);

对函数参数f应用的模板参数推导需要函数调用参数log的类型才能进行。但是日志是重载的,重载的函数集没有类型。

例如,出于同样的原因,这个更简单的代码不应该编译:

#include <algorithm>    // std::for_each
#include <string>       // std::string

void log( std::string const& message );
void log();

auto main()
    -> int
{
    auto const messages = { "Blah blah...", "Duh!", "Oki doki" };
    std::for_each( messages.begin(), messages.end(), log );  //template argument deduction for template parameter Function failed.
}

它可以在这个版本的 MSVC 中工作,因为模板(以前是 /)是作为一种宏实现的,所以 log 作为名称传递,并且在调用 log 的位置执行重载解析在for_each 的正文中。


关于编辑:

表达式!!log 相当于调用bool operator(bool) 没有模板参数推导,编译器只是无法知道它可以使用log 的哪个重载来转换为bool

auto x=y形式的声明中,x的实际类型是通过模板参数推导[dcl.type.auto.deduct]/4推导出来的:

如果占位符是自动类型说明符,则使用模板参数推导规则确定推导的类型 T' 替换 T。 [...]

所以MSVC的行为是错误的,但是是一致的。

【讨论】:

  • 这听起来很合理。我稍后会检查(也许今晚)。谢谢!
  • 我检查了,看不到宏。但我发现 Visual C++ 根据名称 log 的确切使用来检测歧义,即它与自身不一致。请参阅问题末尾的更新。
  • @Cheersandhth.-Alf 我进行了编辑。您的更新显示 MSVC 是一致的。当我说模板被实现为一种宏时,我并不是说模板是宏。
  • 对不起,这对我来说没有意义。
  • @Cheersandhth.-Alf 例如MSVC以前没有两个阶段名称查找msvc article,而且他们仍然有很多模板问题。实际上比较 MSVC 模板和宏不是我的想法,它来自我阅读的一篇文章。不管怎样,MSVC 团队都知道这种不符合标准的行为,正如您在本文中看到的那样,他们正在努力使 MSVC 符合标准。至少我认为没有必要填写错误报告。
【解决方案2】:

定义您自己的::log 会导致未定义的行为(无需诊断)。

来自 C++17 (N4659) [extern.names]/3:

使用外部链接声明的 C 标准库中的每个名称都保留给实现,用于在命名空间 std 和全局命名空间中用作具有外部“C”链接的名称。

Link to related answer.

【讨论】:

  • 这不适用于我的代码,因为 signature: "使用外部链接声明的标准 C 库中的每个函数签名都保留给实现用作函数签名与 extern "C" 和 extern "C++" 链接,或作为全局命名空间中命名空间范围的名称。"但谢谢你的参考。它几乎是重复的(但不完全是!)。
  • 但这会让VC++ 摆脱困境吗?即使您没有定义自己的log() 函数,标准log() 的重载也来自math.h,不是吗?
  • @Cheersandhth.-Alf 你引用了 [extern.names]/4,但是 [extern.names]/3 也适用
  • 现在引用的文本不适用于我的代码,因为 extern "C"linking。但是,再次感谢。我确定我已经知道这一点,但我已经忘记并且正在重新学习。 ;-)
  • @Cheersandhth.-Alf 无论如何它是保留的(用于任何用途),然后链接答案中的最后一条规则适用。
猜你喜欢
  • 1970-01-01
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 2013-05-07
  • 1970-01-01
相关资源
最近更新 更多