此模板功能在哪里生成?可以通过g ++进行编译,但不能在Visual Studio中进行编译

时间:2019-06-02 04:19:36

标签: visual-c++

以下代码无法在我的Visual Studio 2019中编译。但是,如果我删除>>的第一个重载,它将进行编译。 该代码可以在g ++中编译,这使我感到困惑。我猜编译器生成模板函数是在不同的位置?

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;
}

1 个答案:

答案 0 :(得分:2)

潜在的问题是此函数在全局名称空间中签名:

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>>是一个依赖名称,因为它是函数调用的名称,其参数类型取决于模板参数。 / p>

将名称查找应用于从属函数名称(参考:[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 >> nload(sin, poly);不匹配,因此发布的代码将无法编译。
  • 标记为It would compile successfully if this function is removed的行实际上应该没有区别;出于相同的原因,代码仍将无法编译。

结论:在我看来:

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

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

因此我推测该错误可能是overloaded operator name lookup rules的不正确应用-与非操作员名称略有不同,但与此代码示例无关。

有关这些规则的原理和MSVC行为的深入介绍,请参见this excellent article