首页 > 解决方案 > 传递空初始值设定项列表时使用右值和左值引用候选者重载分辨率

问题描述

struct Foo {};
struct Bar {};

int Baz(const Foo&) { return 0; }
int Baz(Bar&&) { return 1; }

int main()
{
  return Baz({});
}

这个称呼有歧义吗?MSVC 选择右值引用重载。GCC 说这是模棱两可的。int Baz(std::vector<Bar>&&)Clang 也选择右值引用,但如果它不是,或者如果 Bar 获得一个采用 std::initializer_list 的构造函数,则不再选择。

是标准{}Bar转换顺序吗?

有人可以解释为什么这适用或不适用:

  1. 标准转换序列 S1 优于标准转换序列 S2,如果

[...]

c) 或者,如果不是这样,S1 和 S2 都绑定到引用参数,而不是引用限定成员函数的隐式对象参数,并且 S1 将右值引用绑定到右值,而 S2 将左值引用绑定到一个右值

https://en.cppreference.com/w/cpp/language/overload_resolution

标签: c++language-lawyer

解决方案


正如问题中已经提到的那样,标准在 [over.ics.rank]/(3.2) 中说:

标准转换序列S1是比标准转换序列更好的转换序列,S2如果

...

  • S1并且S2是引用绑定(11.6.3),既不引用未声明的非静态成员函数的隐式对象参数ref-qualifier,也S1将右值引用绑定到右值并S2绑定左值引用

...

所以 GCC 在这里是错误的。

实际上,可以通过在结构中添加默认构造函数来类似地欺骗 Clang:

struct Foo {
    constexpr Foo() = default;
};
struct Bar {
    constexpr Bar() = default;
};

constexpr int Baz(const Foo&) { return 0; }
constexpr int Baz(Bar&&) { return 1; }

int main() {
  static_assert( Baz({}) == 1 );
}

MSVC是这里正确选择Baz(Bar&&)重载的编译器,demo:https ://gcc.godbolt.org/z/sMcx9ro1x


推荐阅读