如何在模板化转换运算符中消除歧义?

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-qualified T",并且在直接上下文中使用单个参数调用构造函数- 初始化类型为" cv2 T" 的对象,也考虑显式转换函数.

从(1.2)可以看出,只有隐式转换函数被考虑用于初始化,因此模糊性 - 因为编译器无法在f使用引用构造之间做出决定,Foo或者如前所述,使用int*(通过复制初始化获得)从返回值operator int*.但是,当初始化表达式是一个临时对象时,我们也考虑显式转换 - 但只有当它们与引用的构造函数匹配时Foo,我们的"可能是cv-qualified T",即我们的复制和移动构造函数.整个行为与[class.conv.fct2]一致:

转换函数可以是显式的([dcl.fct.spec]),在这种情况下,它仅被视为用户定义的直接初始化转换([dcl.init]).否则,用户定义的转换不限于在分配和初始化中使用.

所以,在这里说同样的事情第3次:如果没有标记为explicit,并没有什么东西试图阻止编译器复制初始化一个int*用于建设.