如何在模板化转换运算符中消除这种结构的歧义? [英] How to disambiguate this construction in a templated conversion operator?

查看:30
本文介绍了如何在模板化转换运算符中消除这种结构的歧义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在对为什么我的代码在 GCC 上给我一个歧义错误而在 Clang 上没有错误感到困惑之后,我简化了代码.可以在下面看到.

After being confused why my code gave me an ambiguity error on GCC but no errors on Clang, I simplified the code. It can be seen below.

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 错误,这是预期的行为.

I was unable to make it successfullycompile for Clang this time, so I suppose that was just a Clang bug and this is the intended behavior.

当我显式删除复制和移动构造函数时(即取消注释前两行代码),我反而得到

When I explicitly delete the copy and move constructors (i.e. uncomment the top two lines of code), I instead get

note: candidate constructor has been explicitly deleted

但仍然是错误.那么,我将如何消除此处的构造歧义?

but still an error. How would I go about disambiguating the construction here, then?

请注意,我特意添加了 Foo{nullptr} 而不是 nullptr,但没有区别.与显式标记 Foo ctor 相同.这种歧义错误仅在 Bar 的转换运算符被模板化时发生.

Note that I specifically added the Foo{nullptr} instead of just nullptr, but there is no difference. Same with marking the Foo ctor explicit. This ambiguity error only occurs when Bar's conversion operator is templated.

我可以向转换运算符添加一些 SFINAE,但我不确定要排除什么.例如,这将使它工作:

I can add some SFINAE to the conversion operator, but I am unsure of what I would exclude. For example, this would make it work:

template<typename T, std::enable_if_t<std::is_same<T, Foo>{}>* = nullptr>

这是我发现的另一个,这可能是我的答案:

This is another one that I found and this might be my answer:

template<typename T, std::enable_if_t<!std::is_same<T, int*>{}>* = nullptr> 

推荐答案

为了解决歧义,将 explicit 添加到转换运算符声明中:

To resolve the ambiguity, add explicit to the conversion operator declaration:

struct Bar
{    
    template<typename T>
    explicit operator T()
    {
        return Foo{nullptr}; 
    }
};

为什么有必要?因为 Foo 有一个带有 int* 的构造函数,所以 operator T() 实例化了 operator int*()code> template 被认为是 f 初始化的重载决议的一部分.参见[over.match.copy]:

Why is it necessary? Because Foo has a constructor taking a int*, and so a operator int*() instantiation of operator T() template is being considered as part of overload resolution for the initialization of f. See under [over.match.copy]:

1 [...] 假设 cv1 T" 是被初始化对象的类型,以T为类类型,候选函数选择如下:

1 [...] Assuming that "cv1 T" is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • (1.1) T 的转换构造函数是候选函数.

  • (1.1) The converting constructors of T are candidate functions.

(1.2) 当初始化表达式的类型为类类型cv S"时,S 及其基类的非显式转换函数是 初始化临时对象([class.mem])时绑定到构造函数的第一个参数,其中参数是类型为对可能有 cv 限定的 T"的引用,构造函数是在直接初始化的上下文中使用单个参数 调用对于类型为cv2 T"的对象,显式转换函数也是

(1.2) When the type of the initializer expression is a class type "cv S", the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type "reference to possibly cv-qualified T" and the constructor is called with a single argument in the context of direct-initialization of an object of type "cv2 T", explicit conversion functions are also considered.

从 (1.2) 可以看出,初始化时只考虑隐式转换函数,因此存在歧义——因为编译器无法决定是否使用对 Foofcode> 或者,如前所述,使用 operator int* 的返回值中的 int*(通过复制初始化获得).然而,初始化表达式是一个临时对象时,我们也会考虑显式转换——但前提是它们匹配一个引用 Foo 的构造函数,我们的 可能是 cv 限定的 T",即我们的 copy移动构造函数.这整个行为与 [class.conv.fct¶2]<一致/a>:

From (1.2) it follows that only implicit conversion functions are considered for the initialization, hence the ambiguity -- as the compiler cannot decide between constructing f using a reference to Foo or, as already mentioned, using a int* (obtained by way of copy-initializing) from the return value of operator int*. However, when the initializer expression is a temporary object, we also consider explicit conversions -- but only if they match a constructor taking a reference to Foo, our "possibly cv-qualified T", i.e. our copy and move constructors. This entire behavior is being consistent with [class.conv.fct¶2]:

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

A conversion function may be explicit ([dcl.fct.spec]), in which case it is only considered as a user-defined conversion for direct-initialization ([dcl.init]). Otherwise, user-defined conversions are not restricted to use in assignments and initializations.

所以,这里第三次说同样的话:如果它没有被标记为explicit,那么没有什么可以阻止编译器尝试复制初始化用于构造的 int*.

And so, saying the same thing here for the 3rd time: if it isn't marked as explicit, there's nothing stopping the compiler from trying to copy-initialize an int* to be used for construction.

这篇关于如何在模板化转换运算符中消除这种结构的歧义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆