涉及模板转换运算符和隐式复制构造函数的歧义

22
clang和gcc在以下代码的行为上有所不同:
struct foo
{
    foo(int);
};

struct waldo
{
    template <typename T>
    operator T();
};

int main()
{
    waldo w;
    foo f{w};
}

这段代码被clang接受,调用了foo(int)构造函数。然而,gcc抱怨foo(int)构造函数和隐式生成的拷贝和移动构造函数之间存在歧义:

test.cpp: In function 'int main()':
test.cpp:15:12: error: call of overloaded 'foo(<brace-enclosed initializer list>)' is ambiguous
     foo f{w};
            ^
test.cpp:15:12: note: candidates are:
test.cpp:3:5: note: foo::foo(int)
     foo(int);
     ^
test.cpp:1:8: note: constexpr foo::foo(const foo&)
 struct foo
        ^
test.cpp:1:8: note: constexpr foo::foo(foo&&)

谁是正确的?

有趣的是,如果将foo f{w}更改为foo f(w)(注意从大括号到圆括号的更改),gcc和clang都会给出错误。这让我希望gcc对上面的示例(即给出错误)的行为是正确的,否则(){}初始化形式之间将存在奇怪的不一致性。

编辑:根据的建议,我尝试delete foo的复制构造函数:

struct foo
{
    foo(int);
    foo(const foo&) = delete;
};

行为保持不变。

1
我会选择使用GCC:这里既可以将其转换为int,也可以将其转换为Foo,因此这看起来确实是有歧义的。不过,尝试删除或默认复制/移动构造函数,然后进行比较。 - Kerrek SB
4
@KerrekSB 删除的函数参与重载决议。 - TemplateRex
4
根据这个演讲的第17页幻灯片显示,显然在C++11中无法使用花括号初始化的方式进行复制,这使得Clang是正确的。这被认为是一个缺陷,可能会在C++14中修复,从而使GCC变得正确。但是,我似乎找不到在C++11中防止其使用的措辞,因此我无法给出确定的答案。 - Mike Seymour
@MatthieuM:不用了,已经有人找到相关的标准文献了。 - Mike Seymour
1
@MikeSeymour:当然是litb :p 顺便感谢Bjarne的演讲,我之前还没看过! - Matthieu M.
显示剩余3条评论
1个回答

11

对于列表初始化,如果列表元素只有一个元素(这里是w),并且考虑使用带有参数“引用到const / volatile X”的类X的构造函数,则不会考虑用户定义的转换。因此,foo的复制和移动构造函数都无法使用。因此,foo(int)构造函数被明确选择。

因此,Clang在这里是正确的。

编辑:对于这里的标准人员,请参见13.3.3.1p4


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接