如何在模板化转换运算符中消除这种结构的歧义? [英] How to disambiguate this construction in a templated conversion operator?
问题描述
在对为什么我的代码在 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, withT
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) 可以看出,初始化时只考虑隐式转换函数,因此存在歧义——因为编译器无法决定是否使用对 Foo的引用来构造
f
code> 或者,如前所述,使用 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屋!