功能完美转发构建功能列表class
Perfect forwarding of functions to build a function list class
考虑以下构建 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)...);
}
我希望这些函数能完美转发(不管是函数指针、函子、lambdas...)。但我并不完全理解 std::forward
和通用引用背后发生的所有类型推导。在上面的代码中,我有三个问题:
-
_f
应该是 std::tuple<F...>
还是 std::tuple<F&&...>
类型(为什么?)
- 是否可以在模板参数列表中推断出 return 类型
R
(因为手动执行而不是 auto/decltype(auto)
将有助于理解正在发生的事情)
- 在 maker 中,
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)...);
}
如果它们是 F&&
,那么如果您将临时值传递给 make_function_list
,包含 tuple
的 returned class 将存储一个右值对传递给 make_function_list
.
的临时引用
在下一行,它现在是悬空引用。
这在大多数用例中似乎很糟糕。这在 all 用例中实际上并不坏; forward_as_tuple
这样做。但此类用例 不是 一般用例。该模式非常脆弱和危险。
一般来说,如果您 return 正在 T&&
,您希望 return 将其作为 T
。这可能会导致对象的副本;但替代方案是 danging-reference-hell.
这给了我们:
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
的 ctor explicit
,因为在 1 个参数的情况下,它转变成一个相当贪婪的隐式转换构造函数。这可以修复,但需要花费更多的精力。
operator()
需要实例。类型名称不是实例。
But I don't exactly understand all the type deduction happening behind std::forward
and universal references.
举个例子就很容易理解了。
template <typename T>
void f(T&&)
{
std::tuple<T>{}; // (0)
std::tuple<T&&>{}; // (1)
}
在(0)的情况下:
T
推导为 T
for rvalues
T
推导为 T&
for lvalues.
如(1):
T
推导为 T&&
for rvalues
T
推导为 T&
for lvalues.
如您所见,两者之间的唯一区别在于 rvalues 的推导方式。
关于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):
x
转换为 T&&
如果 T
推导为 T
.
x
保持 lvalue 否则。
std::forward
通过查看 T
的推导方式,基本上保留了 x
的类型类别。
Should _f be of type std::tuple<F...>
or std::tuple<F&&...>
我认为在你的情况下它应该是 std::tuple<F...>
,因为你想存储 左值引用 或 值 .
std::tuple<F&&...>
将存储 左值引用 或 右值引用 - 这将导致 悬空引用 在临时的情况下。
Is it possible to deduce the return type R
in the template parameter list
是的,就是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)...);
}
In the maker, what the function_list
template argument should be: decltype(std::forward<F>(f)...)
, F
, or F&&...
function_list
应将 F...
作为模板参数,原因在本答案开头列出 (即避免对临时变量的悬空引用)。
它仍然应该以 std::forward<F>(f)...
作为参数,以允许 rvalues 被转发 (即将右值移动到 function_list
的元组).
这取决于 function_list
的用途。基本上有两种情况:
function_list
是一个临时助手,永远不会超过它出现的语句。在这里我们可以存储对函数的引用和 perfect-forward 每个函数的引用到调用点:
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
的 wrapper/container 对象,在这种情况下,您希望存储函数的衰减副本以避免悬挂引用和在这种情况下,perfect-forwarding 意味着将函数转发给 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)...);
}
考虑以下构建 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)...);
}
我希望这些函数能完美转发(不管是函数指针、函子、lambdas...)。但我并不完全理解 std::forward
和通用引用背后发生的所有类型推导。在上面的代码中,我有三个问题:
-
_f
应该是std::tuple<F...>
还是std::tuple<F&&...>
类型(为什么?) - 是否可以在模板参数列表中推断出 return 类型
R
(因为手动执行而不是auto/decltype(auto)
将有助于理解正在发生的事情) - 在 maker 中,
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)...);
}
如果它们是 F&&
,那么如果您将临时值传递给 make_function_list
,包含 tuple
的 returned class 将存储一个右值对传递给 make_function_list
.
在下一行,它现在是悬空引用。
这在大多数用例中似乎很糟糕。这在 all 用例中实际上并不坏; forward_as_tuple
这样做。但此类用例 不是 一般用例。该模式非常脆弱和危险。
一般来说,如果您 return 正在 T&&
,您希望 return 将其作为 T
。这可能会导致对象的副本;但替代方案是 danging-reference-hell.
这给了我们:
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
的 ctor explicit
,因为在 1 个参数的情况下,它转变成一个相当贪婪的隐式转换构造函数。这可以修复,但需要花费更多的精力。
operator()
需要实例。类型名称不是实例。
But I don't exactly understand all the type deduction happening behind
std::forward
and universal references.
举个例子就很容易理解了。
template <typename T>
void f(T&&)
{
std::tuple<T>{}; // (0)
std::tuple<T&&>{}; // (1)
}
在(0)的情况下:
T
推导为T
for rvaluesT
推导为T&
for lvalues.
如(1):
T
推导为T&&
for rvaluesT
推导为T&
for lvalues.
如您所见,两者之间的唯一区别在于 rvalues 的推导方式。
关于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):
x
转换为T&&
如果T
推导为T
.x
保持 lvalue 否则。
std::forward
通过查看 T
的推导方式,基本上保留了 x
的类型类别。
Should _f be of type
std::tuple<F...>
orstd::tuple<F&&...>
我认为在你的情况下它应该是 std::tuple<F...>
,因为你想存储 左值引用 或 值 .
std::tuple<F&&...>
将存储 左值引用 或 右值引用 - 这将导致 悬空引用 在临时的情况下。
Is it possible to deduce the return type
R
in the template parameter list
是的,就是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)...);
}
In the maker, what the
function_list
template argument should be:decltype(std::forward<F>(f)...)
,F
, orF&&...
function_list
应将 F...
作为模板参数,原因在本答案开头列出 (即避免对临时变量的悬空引用)。
它仍然应该以 std::forward<F>(f)...
作为参数,以允许 rvalues 被转发 (即将右值移动到 function_list
的元组).
这取决于 function_list
的用途。基本上有两种情况:
function_list
是一个临时助手,永远不会超过它出现的语句。在这里我们可以存储对函数的引用和 perfect-forward 每个函数的引用到调用点: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
的 wrapper/container 对象,在这种情况下,您希望存储函数的衰减副本以避免悬挂引用和在这种情况下,perfect-forwarding 意味着将函数转发给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)...);
}