完美转发函数以构建函数列表类
Perfect forwarding of functions to build a function list class
请考虑以下构建存储函数的类的代码。
// Function list class
template <class... F>
struct function_list
{
template <class... G>
constexpr function_list(G&&... g) noexcept
: _f{std::forward<G>(g)...}
{
}
std::tuple</* F... OR F&&... */> _f;
};
// Function list maker
template <class... F, class R = /* Can we compute the return type here? */>
constexpr R make_function_list(F&&... f)
{
return function_list<
/* decltype(std::forward<F>(f))...
* OR F...
* OR F&&...
*/>(std::forward<F>(f)...);
}
我希望这些函数被完美地转发(无论它们是函数指针、函子、lambda......但我并不完全理解std::forward
和通用引用背后发生的所有类型推论。在上面的代码中,我有三个问题:
_f
应该是std::tuple<F...>
型还是std::tuple<F&&...>
型(为什么?- 是否可以推断模板参数列表中
R
的返回类型(因为手动而不是auto/decltype(auto)
将有助于了解正在发生的事情) - 在制作器中,
function_list
模板参数应该是什么:decltype(std::forward<F>(f)...)
、F
或F&&...
(为什么?
注意:function_list
的构造函数不是直接调用的,而是make_function_list
在做这项工作。
编辑: 当function_list
的operator()
(此处未显示)不保证在同一声明中被调用时,这种情况是否安全?
template <class... F>
constexpr function_list<F...> make_function_list(F&&... f)
{
return function_list<F&&...>(std::forward<F>(f)...);
}
并不完全理解
std::forward
和通用引用背后发生的所有类型推导。
通过一个例子很容易理解。
template <typename T>
void f(T&&)
{
std::tuple<T>{}; // (0)
std::tuple<T&&>{}; // (1)
}
在(0)的情况下:
T
被推算为右值的T
T
被推算为左值的T&
。
在(1)的情况下:
T
被推算为右值的T&&
T
被推算为左值的T&
。
如您所见,两者之间的唯一区别是如何推导右值。
关于std::forward
,这是它的作用:
template <typename T>
void g(T&&);
template <typename T>
void f(T&& x)
{
g(x) // (0)
g(std::forward<T>(x)); // (1)
}
在(0)的情况下:
x
始终是一个左值。
在(1)的情况下:
如果
T
被推导为T
,则x
被投射到T&&
。否则
x
保持稳定值。
std::forward
基本上保留了x
的类型类别,通过查看如何推断T
。
_f应属于
std::tuple<F...>
型或std::tuple<F&&...>
型
我认为在您的情况下它应该是std::tuple<F...>
,因为您想要存储左值引用或值。
std::tuple<F&&...>
将存储右值引用或右值引用- 这将导致在临时情况下悬空引用。
是否可以推断出模板参数列表中的返回类型
R
是的,这只是function_list<F...>
.
template <class... F, class R = function_list<F...>>
constexpr R make_function_list(F&&... f)
{
return function_list<F...>(std::forward<F>(f)...);
}
您甚至不需要R
模板参数。
template <class... F>
constexpr function_list<F...> make_function_list(F&&... f)
{
return function_list<F...>(std::forward<F>(f)...);
}
在制作器中,
function_list
模板参数应该是什么:decltype(std::forward<F>(f)...)
、F
或F&&...
function_list
应将F...
作为模板参数,原因在此答案的开头列出(即避免悬而未决地引用临时)。
它仍然应该以std::forward<F>(f)...
作为它的参数,以允许右值被转发(即将右值移动到function_list
元
如果它们是F&&
的,那么如果你将一个临时传递给make_function_list
,则包含tuple
的返回类将存储对传递给make_function_list
的临时的右值引用。
在下一行,它现在是一个悬而未决的引用。
这在大多数用例中似乎很糟糕。 这在所有用例中实际上并不坏;forward_as_tuple
这样做。 但这样的用例不是一般的用例。 该图案非常脆弱和危险。
通常,如果您要返回T&&
,则希望将其作为T
返回。 这可能会导致对象的副本;但另一种选择是悬而未决的参考地狱。
这为我们提供了:
template<class... Fs>
struct function_list {
template<class... Gs>
explicit constexpr function_list(Gs&&... gs) noexcept
: fs(std::forward<Gs>(gs)...)
{}
std::tuple<Fs...> fs;
};
template<class... Fs, class R = function_list<Fs...>>
constexpr R make_function_list(Fs&&... fs) {
return R(std::forward<Fs>(fs)...);
}
也使function_list
的 ctorexplicit
,因为在 1 参数的情况下,它演变成一个相当贪婪的隐式转换构造函数。 这可以修复,但需要付出更多的努力。
operator()
需要一个实例。 类型名称不是实例。
这取决于function_list
的用途。基本上有两种情况:
-
function_list
是一个临时帮助程序,它永远不会超过它出现的语句。在这里,我们可以存储对函数的引用,并将每个函数完美转发到调用点:template <class... F> struct function_list { std::tuple<F&&...> f_; // no need to make this template constexpr function_list(F&&... f) noexcept : f_{std::forward<F>(f)...} {} template <std::size_t i, typename... A> decltype(auto) call_at(A&&... a) { return std::invoke(std::get<i>(f_), std::forward<A>(a)...); } };
-
function_list
是一个类似于std::bind
的包装器/容器对象,在这种情况下,您需要存储函数的衰减副本以避免悬而未决的引用,在这种情况下,完美转发意味着将函数转发给f_
中其衰减版本的构造函数,然后在调用时将衰减的函数赋予function_list
本身的值类别:template <class... F> struct function_list { std::tuple<std::decay_t<F>...> f_; template <typename... G> constexpr function_list(G&&... g) : f_{std::forward<G>(g)...} {} template <std::size_t i, typename... A> decltype(auto) call_at(A&&... a) & { return std::invoke(std::get<i>(f_), std::forward<A>(a)...); } template <std::size_t i, typename... A> decltype(auto) call_at(A&&... a) const& { return std::invoke(std::get<i>(f_), std::forward<A>(a)...); } template <std::size_t i, typename... A> decltype(auto) call_at(A&&... a) && { return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); } template <std::size_t i, typename... A> decltype(auto) call_at(A&&... a) const&& { return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); } };
与
std::bind
一样,如果您确实要存储引用,则必须使用std::reference_wrapper
显式执行此操作。
两种情况下的结构相同:
template <class... F>
constexpr auto make_function_list(F&&... f)
{
return function_list<F...>(std::forward<F>(f)...);
}
- 如何在C++中泛化调用函数列表?
- 不带初始值设定项的构造函数列表,其中包含带有已删除构造函数的对象
- 具有可变参数的std ::函数列表如何工作
- 空向量的构造函数列表初始化
- C++ 中的构造函数列表
- 为什么初始化构造函数列表参数时会发生异常?
- 完美转发函数以构建函数列表类
- 如何在 c++ 中将函数列表应用于字符串
- 什么是结构开头的函数列表指针,称为 c++
- <函数列表>的多重定义
- 指向函数(列表)的指针
- 如何使用Boost预处理器获取类函数可访问的函数列表
- 避免使用容器调用函数列表
- 提取头文件中的函数列表
- 提升::p tr_vector 成员函数列表
- 完整的函数列表,来自c ++的MS Word命令
- 获取DLL中定义的函数列表
- 在运行时获取名称空间中的函数列表
- 如何在类构造函数列表中初始化结构
- 在c++中使用函数列表来调用async