c++ - 这个模板函数是在哪里生成的呢?可以通过 g++ 编译,但不能在 Visual Studio 中编译

标签 c++ templates gcc visual-c++ argument-dependent-lookup

以下代码无法在我的 Visual Studio 2019 中编译。但是如果我删除 >> 的第一个重载,它就会编译。

代码可以通过 g++ 编译,这让我很困惑。我猜这是关于编译器生成模板函数的不同位置?

错误消息:错误 C2679:二进制“>>”:找不到采用 'std::vector<int,std::allocator<_T>>' 类型的右侧操作数的运算符

#include <iostream>
#include <vector>
typedef std::vector<int> Mon;  // ordered
typedef std::vector<Mon> Poly;  // ordered

class A {};

// It would compile successfuly if this function is removed
std::istream& operator>>(std::istream& sin, A& a)
{
    return sin;
}

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

std::istream& operator>>(std::istream& sin, Mon& mon)
{
    load(sin, mon);
    return sin;
}

std::istream& operator>>(std::istream& sin, Poly& poly)
{
    load(sin, poly);
    return sin;
}

int main()
{
    return 0;
}

最佳答案

根本问题是全局命名空间中的这个函数签名:

std::istream& operator>>(std::istream& sin, std::vector<int>& mon);

无法通过参数相关查找找到。由于所有参数都在 std 中,ADL 仅搜索 std而不是全局命名空间。
为了避免此类问题,您可以遵循一条经验法则:不要以 ADL 找不到它们的方式重载运算符。 (推论:你不应该尝试让 vector<Foo> v; cin >> v; 工作)。


首先,请注意语法 sin >> n翻译为执行 operator>>(sin, n)sin.operator>>(n)并结合所有结果,as described in full here .

问题中的代码与 this question 非常相似我将总结那里的最佳答案的发现。

对于此功能:

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

特别是当查找 operator>>(sin, n) 时发生了,operator>>是一个依赖名称,因为它是参数类型取决于模板参数的函数调用的名称。

当名称查找应用于依赖函数名称时(引用:[temp.dep.candidate]),规则为:

  1. 考虑模板定义时可见的任何函数声明。
  2. 考虑 ADL 在实例化时找到的任何函数声明。
  3. 如果有extern程序中其他地方定义的函数如果在实例化时具有可见声明,则 ADL 会发现这些函数,并且这些额外的声明会影响重载解析,则程序具有未定义的行为(无需诊断)。<

(注意:我的这个答案的第一个版本错误地引用了规则 3,因此得出了错误的结论):

所以查找sin >> n从调用 load(sin, mon); 实例化成功是因为成员函数 std::istream::operator>>(int&)被发现。 (搜索还找到了 A& 版本,但重载解析选择了成员函数)。

查找 sin >> n 时出现问题实例化load(sin, poly);

根据规则 1,operator>>(std::istream&, A&)被发现。 (这个函数稍后会被重载解析丢弃,但在这个阶段我们只是执行名称查找)。

根据规则2,ADL命名空间列表为:std 。所以这一步会找到std::operator>> (各种重载),但不是::operator>>(istream&, Mon&);因为它不在命名空间 std 中.

规则 3 不适用,因为 namespace std 中没有任何重载。它将接受 Mon .

所以正确的行为是:

  • 由于与 sin >> n 不匹配,发布的代码应该会编译失败。实例化时load(sin, poly); .
  • 标记为 It would compile successfully if this function is removed 的行实际上应该没有区别;由于同样的原因,代码仍应编译失败。

结论:在我看来:

  • clang 8.0.0 行为正确。
  • msvc 正确拒绝发布的代码,但错误地接受修改后的版本。
  • gcc 9.1.0 错误地接受这两个版本;

我注意到,如果我们更改 operator>>barsin >> nbar(sin, n);那么 gcc 和 msvc 正确地拒绝这两个版本。 gcc 甚至给出了与 clang 非常相似的错误消息。

所以我推测该错误可能是 overloaded operator name lookup rules 的错误应用-- 与非操作符名称略有不同,但与此代码示例没有任何关系。

有关这些规则的基本原理和 MSVC 行为的深入文章,请参阅 this excellent article .

关于c++ - 这个模板函数是在哪里生成的呢?可以通过 g++ 编译,但不能在 Visual Studio 中编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56412168/

相关文章:

c++ - 类型推导后函数模板中的替换顺序是否有任何保证?

objective-c - GCC -fobjc-direct-dispatch 选项究竟做了什么?

C - pthread_join() 挂起(有时)

c++ - 是否可以为 Visual Studio Code 安装多个 CMake 扩展?

android - android中的g++和ubuntu中的g++有什么区别?

c++ - 具有模板参数的模板特化

c++ - 是否可以将带有捕获和参数的 lambda 传递给另一个函数?如果是这样,如何?

c++ - 转发引用可以使用别名模板作为别名吗?

c - ARM 的 gcc 中是否使用了分支谓词?我们如何禁用它?

c++ - 使用 Qt 在后台线程中定期执行某些 lambda func 的正确方法是什么?