c++ - 在重载解析期间调用转换运算符而不是转换 C++17 中的构造函数

标签 c++ c++14 language-lawyer c++17

考虑以下两个类:

#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << '\n')

struct D;

struct C {
    C() { PRETTY(this);}
    C(const C&) { PRETTY(this);}
    C(const D&) { PRETTY(this);}
};

struct D {
    D() { PRETTY(this);}
    operator C() { PRETTY(this); return C();}
};

我们对两个构造函数之间的重载决议感兴趣:

C::C(const C&);
C::C(const D&);

此代码按预期工作:

void f(const C& c){ PRETTY(&c);}
void f(const D& d){ PRETTY(&d);}
/*--------*/
D d;
f(d); //calls void f(const D& d)

因为 void f(const D& d) 是更好的匹配。

但是:

D d;
C c(d);

(如您所见 here )

std=c++17编译时调用D::operator C(),调用C::C(const D&)在 clang 和 gcc HEAD 上使用 std=c++14 的 code>。 此外,这种行为最近发生了变化: 使用 clang 5,使用 std=c++17std=c++14 调用 C::C(const D&) .

这里的正确行为是什么?

标准试读(最新草案 N4687):

C c(d) 是直接初始化,不是复制省略 ([dcl.init]/17.6.1)。 [dcl.init]/17.6.2 告诉我们枚举了适用的构造函数,并且通过重载决议选择了最好的构造函数。 [over.match.ctor] 告诉我们适用的构造函数在这种情况下是所有构造函数。

在这种情况下:C()C(const C&)C(const D&)(无移动 ctor)。 C() 显然不可行,因此从重载集中被丢弃。 ([over.match.viable])

构造函数没有隐式对象参数,因此 C(const C&)C(const D&) 都只采用一个参数。 ([over.match.funcs]/2)

我们现在转到 [over.match.best]。在这里我们发现我们需要确定哪些 这两个隐式转换序列(ICS)更好。 ICS 的 C(const D&) 只涉及标准转换序列,而C(const C&) 的ICS 涉及用户自定义转换序列。

因此应该选择 C(const D&) 而不是 C(const C&)


有趣的是,这两个修改都导致“正确的”构造函数 被称为:

operator C() {/* */} 转换成 operator C() const {/* */}

C(const D&) {/* */} 转化为 C(D&) {/* */}

这就是(我认为)在复制初始化情况下会发生的情况 用户定义的转换和转换构造函数会过载 分辨率。


根据 Columbo 的建议,我向 gcc 和 clang 提交了错误报告

gcc 错误 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82840

clang 错误 https://bugs.llvm.org/show_bug.cgi?id=35207

最佳答案

Core issue 243 (17 岁!):

There is a moderately serious problem with the definition of overload resolution. Consider this example:

struct B;
struct A {
    A(B);
};
struct B {
    operator A();
} b;
int main() {
    (void)A(b);
} 

This is pretty much the definition of "ambiguous," right? You want to convert a B to an A, and there are two equally good ways of doing that: a constructor of A that takes a B, and a conversion function of B that returns an A.

What we discover when we trace this through the standard, unfortunately, is that the constructor is favored over the conversion function. The definition of direct-initialization (the parenthesized form) of a class considers only constructors of that class. In this case, the constructors are the A(B) constructor and the (implicitly-generated) A(const A&) copy constructor. Here's how they are ranked on the argument match:

  • A(B): exact match (need a B, have a B)
  • A(const A&): user-defined conversion (B::operator A used to convert B to A)

In other words, the conversion function does get considered, but it's operating with, in effect, a handicap of one user defined conversion. To put that a different way, this problem is a problem of weighting, not a problem that certain conversion paths are not considered. […]

Notes from 10/01 meeting:

It turns out that there is existing practice both ways on this issue, so it's not clear that it is "broken". There is some reason to feel that something that looks like a "constructor call" should call a constructor if possible, rather than a conversion function. The CWG decided to leave it alone.

看来你是对的。此外,无论是根据措辞还是直觉,Clang 和 GCC 选择转换运算符都不是最佳选择,因此除非这是由于向后兼容性(即使是这样),否则错误报告是合适的。

关于c++ - 在重载解析期间调用转换运算符而不是转换 C++17 中的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47110853/

相关文章:

c - C 中命令行参数 `argv` 的类型是什么?

c++为什么当我不使用内联时链接会失败?

c++ - 在不同的范围内新建和删除

c++ - 引用具有与模板不兼容的模板参数的 C++ 模板类型是否安全?

c++ - 当部分特化的参数不使用其任何模板参数时适用哪些规则

c++ - 指向类成员的指针作为模板参数

c++ - 当两个类相互引用时编译 C++

c++ - 需要帮助理解 vector 如何以二进制表示 [C++]

c++ - 'using' 和 'using namespace' 之间的区别

c++ - 如果存在从 `double`到 `T`的转换,SFINAE禁用构造函数