我在一个std::optional
实现中遇到了这个代码:
template <class T, class U>
struct is_assignable
{
template <class X, class Y>
constexpr static bool has_assign(...) { return false; }
template <class X, class Y, size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true)) >
// the comma operator is necessary for the cases where operator= returns void
constexpr static bool has_assign(bool) { return true; }
constexpr static bool value = has_assign<T, U>(true);
};
Run Code Online (Sandbox Code Playgroud)
我无法理解它是如何工作或如何评估的部分是size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true))
我知道如果赋值操作失败,它将回退到返回false的has_assign的第一个定义,但我不知道为什么它有该, true)
部分.
我使用在assign操作符上返回void的结构进行了一些测试,并删除了, true
部分sizeof
给出了相同的结果.
原则上,表达式的类型std::declval<X>() = std::declval<Y>()
(即所operator =
涉及的返回类型)可以是任意的,包括不完整的类型或void
.在这种情况下,SFINAE不会启动,因为表达式是有效的.但是,您应用于sizeof
不完整类型时会收到错误.(请注意,某些编译器定义sizeof(void) == 1
为扩展,但不能普遍依赖).
, true
在SFINAE表达式之后添加通过丢弃赋值的类型(无论它是什么)以及应用于(这是完全有效的)sizeof
来修复此问题true
.
正如Barry在评论中指出的那样,更直接的方法是使用赋值类型decltype
而不是in sizeof
,如下所示:
template <class X, class Y, class S = decltype(std::declval<X>() = std::declval<Y>()) >
constexpr static bool has_assign(bool) { return true; }
Run Code Online (Sandbox Code Playgroud)
要申请sizeof()
,您需要一个完整的类型.但是返回完整类型不是可赋值的要求,因此:
sizeof((std::declval<X>() = std::declval<Y>(), true))
~~~~~~~~~~~~~~~~~~ expr ~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
如果赋值对这两种类型有效,那么我们sizeof(expr)
的类型expr
是bool
(因为true
).因此,如果作业有效,我们会得到一些真实的size
.否则,替换失败.
但这是编写此代码的一种不必要的神秘方式.而且,它甚至不正确,因为我可以写一个类似的类型:
struct Evil {
template <class T> Evil operator=(T&& ); // assignable from anything
void operator,(bool); // mwahahaha
};
Run Code Online (Sandbox Code Playgroud)
现在你sizeof()
仍然无法正常工作.
相反,更喜欢简单:
class = decltype(std::declval<X>() = std::declval<Y>())
Run Code Online (Sandbox Code Playgroud)
这实现了相同的结果 - 替换失败与否 - 无需关心结果的类型或处理特殊情况.