如何在模板转换运算符中消除这种结构的歧义?

时间:2018-08-22 18:06:14

标签: 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{}}; }

错误如下。

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> 

2 个答案:

答案 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 :(得分:0)

经过一番挖掘,我的最佳猜测:我收到以下代码相同的错误:

struct Foo { Foo(int*) {} };

struct Bar {    
   operator Foo(); // { return Foo{nullptr}; }
   /* explicit */ operator int*();
};

int main() { Foo f{Bar{}}; }

而且,当我取消注释注释的代码时,问题就消失了。在我看来,在OP的原始模板版本中,当需要从BarFoo隐式转换时,GCC仅“实例化”转换操作符声明,然后在实例化其主体之前解决重载。

关于explicit为何起作用的原因,是因为在第二种情况下,还需要进行一次转换(Barint*,然后是int*→{{1 }}。