Pos*_*elf 12 c++ templates type-conversion ambiguous
在混淆之后为什么我的代码在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{}}; }
Run Code Online (Sandbox Code Playgroud)
错误如下.
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*) {}
^
Run Code Online (Sandbox Code Playgroud)
这次我无法让它成功地为Clang编译,所以我想这只是一个Clang bug而且这是预期的行为.
当我明确删除副本并移动构造函数(即取消注释前两行代码)时,我得到了
note: candidate constructor has been explicitly deleted
Run Code Online (Sandbox Code Playgroud)
但仍然是一个错误.那我怎么去消除这里的建筑歧义呢?
请注意,我特意添加了Foo{nullptr}而不仅仅是nullptr,但没有区别.与Foo明确标记ctor 相同.这种模糊度错误仅在Bar模板化转换运算符时发生.
我可以在转换运算符中添加一些SFINAE,但我不确定我会排除什么.例如,这会使它工作:
template<typename T, std::enable_if_t<std::is_same<T, Foo>{}>* = nullptr>
Run Code Online (Sandbox Code Playgroud)
这是我发现的另一个,这可能是我的答案:
template<typename T, std::enable_if_t<!std::is_same<T, int*>{}>* = nullptr>
Run Code Online (Sandbox Code Playgroud)
Rei*_*ica 11
要解决歧义,请添加explicit到转换运算符声明:
struct Bar
{
template<typename T>
explicit operator T()
{
return Foo{nullptr};
}
};
Run Code Online (Sandbox Code Playgroud)
为什么有必要?因为Foo有一个构造函数取一个int*,所以模板的operator int*()实例化被认为是初始化的重载解析的一部分.见[over.match.copy]:operator T() f
1 [...]假设"
cv1 T"是要初始化的对象的类型,使用T类类型,候选函数选择如下:
(1.1)转换构造
T函数是候选函数.(1.2)当初始化表达式的类型是类类型"
cv S"时, 考虑非显式转换函数S及其基类.初始化临时对象([class.mem])以绑定到构造函数的第一个参数时,其中参数的类型为"引用可能的cv-qualifiedT",并且在直接上下文中使用单个参数调用构造函数- 初始化类型为"cv2 T" 的对象,也考虑显式转换函数.
从(1.2)可以看出,只有隐式转换函数被考虑用于初始化,因此模糊性 - 因为编译器无法在f使用引用构造之间做出决定,Foo或者如前所述,使用int*(通过复制初始化获得)从返回值operator int*.但是,当初始化表达式是一个临时对象时,我们也考虑显式转换 - 但只有当它们与引用的构造函数匹配时Foo,我们的"可能是cv-qualified T",即我们的复制和移动构造函数.整个行为与[class.conv.fct2]一致:
转换函数可以是显式的([dcl.fct.spec]),在这种情况下,它仅被视为用户定义的直接初始化转换([dcl.init]).否则,用户定义的转换不限于在分配和初始化中使用.
所以,在这里说同样的事情第3次:如果没有标记为explicit,并没有什么东西试图阻止编译器复制初始化一个int*用于建设.