在我的代码为什么让我在GCC上出现歧义错误但在Clang上没有错误之后,我简化了代码。可以在下面看到。
struct Foo
{
// Foo(Foo&&) = delete;
// Foo(const Foo&) = delete;
Foo(int*) {}
};
struct Bar
{
template<typename T>
operator T()
{
return Foo{nullptr};
}
};
int main() { Foo f{Bar{}}; }
错误如下。
main.cpp:17:18: error: call to constructor of 'Foo' is ambiguous
int main() { Foo f{Bar{}}; }
^~~~~~~~
main.cpp:1:8: note: candidate is the implicit move constructor
struct Foo
^
main.cpp:1:8: note: candidate is the implicit copy constructor
main.cpp:5:1: note: candidate constructor
Foo(int*) {}
^
这次我无法使其成功地为Clang编译,所以我认为那只是Clang的错误,这是预期的行为。
当我明确删除复制并移动构造函数(即取消注释前两行代码)时,我得到了
note: candidate constructor has been explicitly deleted
但仍然是错误。那我该如何消除这里的结构歧义?
请注意,我专门添加了Foo{nullptr}
而不只是添加了nullptr
,但没有区别。与显式标记Foo
ctor相同。仅当Bar
的转换运算符已模板化时,才会发生这种歧义错误。
我可以在转换运算符中添加一些SFINAE,但是我不确定要排除的内容。例如,这将使其起作用:
template<typename T, std::enable_if_t<std::is_same<T, Foo>{}>* = nullptr>
这是我发现的另一个,这可能是我的答案:
template<typename T, std::enable_if_t<!std::is_same<T, int*>{}>* = nullptr>
答案 0 :(得分:11)
要解决歧义,请将explicit
添加到转换运算符声明中:
struct Bar
{
template<typename T>
explicit operator T()
{
return Foo{nullptr};
}
};
为什么有必要?由于Foo
的构造函数采用int*
,因此operator int*()
template 的operator T()
实例被认为是重载解析的一部分。 f
的初始化。参见[over.match.copy]下的内容:
1 [...]假设“
cv1 T
” 是要初始化的对象的类型, 在T
为类类型的情况下,候选函数的选择如下:
(1.1)
T
的转换构造函数是候选函数。(1.2)当初始化器表达式的类型为类类型“
cv S
”时,S
及其基类的非显式转换函数是 。在初始化临时对象([class.mem])时 绑定到构造函数的第一个参数,其中参数为 类型为“对可能具有cv资格的T
的引用”,构造函数为 在直接初始化的上下文中使用单个参数调用 类型“cv2 T
”的对象,显式转换函数也是 考虑过。
从(1.2)开始,仅考虑隐式转换函数进行初始化,因此存在歧义-因为编译器无法在使用对f
的引用构造Foo
之间进行决定,或者已经确定提到,使用int*
的返回值中的operator int*
(通过 copy-initialization 获得)。 但是,当 initializer表达式是临时对象时,我们也会考虑显式转换-但前提是它们必须是与引用Foo
的构造函数匹配,即我们的“可能具有简历资格的T
” ,即我们的 copy 和 move构造函数< / em> s。整个行为与[class.conv.fct¶2]一致:
在这种情况下,转换函数可能是显式的([dcl.fct.spec]) 它仅被视为用户定义的转换 直接初始化([dcl.init])。 否则,由用户定义 转换不限于用于作业和 初始化。
因此,这是第三次在这里说同样的话:如果未将其标记为explicit
,则没有任何阻止编译器尝试<{> copy-initialize { 1}}用于建筑。
答案 1 :(得分:0)
经过一番挖掘,我的最佳猜测:我收到以下代码相同的错误:
struct Foo { Foo(int*) {} };
struct Bar {
operator Foo(); // { return Foo{nullptr}; }
/* explicit */ operator int*();
};
int main() { Foo f{Bar{}}; }
而且,当我取消注释注释的代码时,问题就消失了。在我看来,在OP的原始模板版本中,当需要从Bar
到Foo
隐式转换时,GCC仅“实例化”转换操作符声明,然后在实例化其主体之前解决重载。>
关于explicit
为何起作用的原因,是因为在第二种情况下,还需要进行一次转换(Bar
→int*
,然后是int*
→{{1 }}。