使用变体将左值或右值存储在同一对象中

get*_*ubl 0 c++ templates variant variadic-templates c++17

我正在阅读一种用于在同一对象中存储左值或右值的技术。请在此处找到对此的描述 。重载技巧已实现。

然而,当定义重载结构体时,会定义一个加法重载构造函数,其中将 lambda 衰减时生成的函数指针作为参数传递

template<typename... Functions>
struct overload : Functions...
{
    using Functions::operator()...;
    overload(Functions... functions) : Functions(functions)... {}
};
Run Code Online (Sandbox Code Playgroud)

为什么需要显式定义这个构造函数?

这里有一个现场演示

使用cppinsights分析代码,上述代码的模板实例化如下

inline overload(__lambda_39_13 __functions0, __lambda_40_13 __functions1, __lambda_41_13 __functions2)
  : __lambda_39_13(__functions0)
  , __lambda_40_13(__functions1)
  , __lambda_41_13(__functions2)
  {
  }
Run Code Online (Sandbox Code Playgroud)

我不清楚这个模板实例化是如何生成的。

使用显式自定义推导指南无法编译

看看下面修改后的演示

产生以下错误

:38:9: 错误: 没有匹配的函数来调用 'overload&) [with T = ...

use*_*522 5

您尝试overload通过括号初始化进行初始化,例如此处:

overload(
    [](Value<T> const& value) -> T const&             { return value.value_; },
    [](NonConstReference<T> const& value) -> T const& { return value.value_; },
    [](ConstReference<T> const& value) -> T const&    { return value.value_; }
)
Run Code Online (Sandbox Code Playgroud)

在 C++17 中,带有两个或多个参数的带括号初始化始终需要一个可用的构造函数来接受初始值设定项中的参数。如果您没有在overloadthen 中声明任何构造函数,则不可能工作,因为隐式声明的构造函数只能是默认构造函数、复制构造函数或移动构造函数,它们都不接受三个参数。您也没有从overload的基类继承任何构造函数(即使您继承了,这也无济于事)。

无论模板参数如何决定,推导指南都无法改变没有构造函数可供选择的事实。

但是,如果未声明构造函数,则它overload是一个聚合类,您可以使用聚合初始化而不是通过构造函数进行初始化。聚合初始化不会调用任何构造函数,而是按照初始化器列表中的初始化器的顺序一一初始化基类子对象(如果有的话,后面是直接成员子对象)。这仅要求您在 C++17 中使用大括号而不是圆括号。C++17 中不考虑带括号的聚合初始化:

overload{
    [](Value<T> const& value) -> T const&             { return value.value_; },
    [](NonConstReference<T> const& value) -> T const& { return value.value_; },
    [](ConstReference<T> const& value) -> T const&    { return value.value_; }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,在 C++17 中,没有用于聚合初始化的自动推导指南,因此您仍然必须添加在修改后的演示中建议的推导。

如果您使用的是 C++20(以及完全实现它的编译器),那么您既不需要推导指南,也不需要大括号来使用聚合初始化,因为引入了用于聚合初始化的足够的自动推导指南,并且聚合初始化用括号使之成为可能。原始版本无需定义构造函数即可工作。