隐式转换运算符与模板构造函数 - 谁应该优先考虑?



请考虑以下代码片段:

template <typename>
struct dependent_false { static constexpr auto value = false; };
struct foo
{
foo() { }
template <typename T>
foo(const T&) { static_assert(dependent_false<T>::value, ""); }
};
struct proxy
{
operator foo() { return foo{};  }
};
int main()
{
(void) foo{proxy{}};
}

使用-std=c++17编译时:

  • clang++(主干(成功编译代码;

  • g++(主干(无法编译代码 - 它实例化foo(const T&).

当用-std=c++11编译时,两个编译器都拒绝代码。C++17 中的新prvalue具体化规则可能会影响此处的行为。

godbolt.org 上的活生生的例子


这里的正确行为是什么?

  • 标准是否保证foo::foo(const T&)将被(或不(实例化?

  • 标准是否保证隐式转换运算符优先于foo::foo(const T&)的调用,无论它是否被实例化?

这是CWG 2327:

考虑如下示例:

struct Cat {};
struct Dog { operator Cat(); };
Dog d;
Cat c(d);

这转到 11.6 [dcl.init] 项目符号 17.6.2:

否则,如果初始化是直接初始化,或者如果是复制初始化,其中源类型的 cv 非限定版本与目标类的类相同或派生类,则考虑构造函数。枚举适用的构造函数(16.3.1.3 [over.match.ctor](,并通过重载解析(16.3 [over.match](选择最佳构造函数。调用如此选择的构造函数来初始化对象,并使用初始值设定项表达式或表达式列表作为其参数。如果未应用构造函数,或者重载解析不明确,则初始化格式不正确。

重载解析选择 Cat 的移动构造函数。初始化构造函数的 Cat&& 参数会导致临时的,根据 11.6.3 [dcl.init.ref] 项目符号 5.2.1.2。这排除了本案复制省略的可能性。

这似乎是对保证副本省略的措辞更改的疏忽。在这种情况下,我们大概应该同时考虑构造函数和转换函数,就像我们对复制初始化所做的那样,但我们需要确保这不会引入任何新的问题或歧义。

我相信 clang 实现了这种隐含的更改(因此认为转换函数是更好的匹配(,而 gcc 没有(因此从未真正考虑过转换函数(。

根据标准,gcc是正确的。

最新更新