完美转发 class 模板参数推导
Perfect forwarding with class template argument deduction
我想了解演绎指南如何与通用引用和 std::forward
一起使用,特别是如何创建完美的转发包装器。下面的代码提供了在两种情况下使用函子包装器进行试验的代码:一种使用隐式推导指南,另一种使用显式推导指南。
我在评论里放了很多&&
和std::forward
,因为不知道哪里需要它们才能实现完美转发。我想知道放在哪里,不需要放在哪里。
// Case with not conversion constructor
template <class F>
struct functor1
{
explicit constexpr functor1(F/*&&*/ f)
noexcept(std::is_nothrow_copy_constructible_v<F/*&&*/>)
: _f(/*std::forward<F>(*/f/*)*/)
{}
template <class... Args>
constexpr operator()(Args&&... args)
noexcept(std::is_nothrow_invocable_v<F/*&&*/, Args/*&&*/...>)
{
/*std::forward<F>(*/_f/*)*/(std::forward<Args>(args)...);
}
private: F/*&&*/ _f;
};
// Case with a conversion constructor
template <class F>
struct functor2
{
template <class G>
explicit constexpr functor2(G&& g)
noexcept(std::is_nothrow_constructible_v<G/*&&*/, F/*&&*/>)
: _f(/*std::forward<G>(*/g/*)*/)
{}
template <class... Args>
constexpr operator()(Args&&... args)
noexcept(std::is_nothrow_invocable_v<F/*&&*/, Args/*&&*/...>)
{
/*std::forward<F>(*/_f/*)*/(std::forward<Args>(args)...);
}
private: F/*&&*/ _f;
};
template <class G>
functor2(G&&) -> functor2<G/*&&*/>;
编辑:为了简单起见,因为这不是问题的重点,在前面的例子中,我们认为 F
和 G
是函数对象,即 classes/structs 与 operator()
.
C++ 标准定义了术语转发引用。我想 universal reference 用作该术语的同义词。 [temp.deduct.call]/3
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template.
此概念仅适用于模板函数参数或模板构造函数参数。在所有其他情况下,T&&
是 右值引用 。 转发引用 的概念仅对模板参数推导有用。让我们考虑在以下示例中,所有函数和构造函数都使用 int
参数调用(独立于其常量和值类别 (lvalue/rvalue):
//possibilities of argument deduction, [cv] means any combination of "const" and "volatile":
// <"","const","volatile","const volatile">
template<class T> void f(T&);
//4 possibilities: void f([cv] int&);
template<class T> void f(const T&);
//2 possibilities: void f(const int&);
//void f(const volatile int&);
template<class T> void f(T&&);
//Forwarding reference, 8 possibilities
//void f([cv] int&);
//void f([cv] int&&);
template<class T> void f(const T&&);
//NOT a forwarding reference because of the const qualifier, 2 possibilities:
//void f(const int&&);
//void f(const volatile int&&);
template<class T>
struct S{
template<class U>
S(U&&);
//Forwarding reference, 8 posibilities:
//void S<X>([cv] int&);
//void S<X>([cv] int&&);
//no template argument deduction posible
S(T&&);
//NOT a forwarding reference, 1 possibility:
//void S<X>(X&&);
//Generated argument deduction:
//template<class T> S(T&&) -> S<T>;
//not a forwarding reference because T is a parameter of the template class;
//=> 4 possibilities: -> S<[cv] int&&>
T&& a; //an rvalue reference if T is [cv] int or [cv] int&&,
//an lvalue reference if T is [cv] int&;
//This comes from reference colapsing rules: &+&=&; &&+&=&; &&+&&=&& //(Nota: You may consider that a rvalue reference data member is probably a mistake)
};
template<class U>
S(U&&) -> S<U&&>;
//Forwarding reference, 8 possibilities:
// S<[cv] int&>;
// S<[cv] int&&>;
如果 std::forward
的参数可以是右值引用或左值引用,则使用 std::forward
仅在函数体内或构造函数内部有意义,具体取决于模板参数推导和引用崩溃的规则。如果 std::forward
的参数总是导致右值引用,则首选 std::move
,如果它总是导致左值引用,则不首选。
我想了解演绎指南如何与通用引用和 std::forward
一起使用,特别是如何创建完美的转发包装器。下面的代码提供了在两种情况下使用函子包装器进行试验的代码:一种使用隐式推导指南,另一种使用显式推导指南。
我在评论里放了很多&&
和std::forward
,因为不知道哪里需要它们才能实现完美转发。我想知道放在哪里,不需要放在哪里。
// Case with not conversion constructor
template <class F>
struct functor1
{
explicit constexpr functor1(F/*&&*/ f)
noexcept(std::is_nothrow_copy_constructible_v<F/*&&*/>)
: _f(/*std::forward<F>(*/f/*)*/)
{}
template <class... Args>
constexpr operator()(Args&&... args)
noexcept(std::is_nothrow_invocable_v<F/*&&*/, Args/*&&*/...>)
{
/*std::forward<F>(*/_f/*)*/(std::forward<Args>(args)...);
}
private: F/*&&*/ _f;
};
// Case with a conversion constructor
template <class F>
struct functor2
{
template <class G>
explicit constexpr functor2(G&& g)
noexcept(std::is_nothrow_constructible_v<G/*&&*/, F/*&&*/>)
: _f(/*std::forward<G>(*/g/*)*/)
{}
template <class... Args>
constexpr operator()(Args&&... args)
noexcept(std::is_nothrow_invocable_v<F/*&&*/, Args/*&&*/...>)
{
/*std::forward<F>(*/_f/*)*/(std::forward<Args>(args)...);
}
private: F/*&&*/ _f;
};
template <class G>
functor2(G&&) -> functor2<G/*&&*/>;
编辑:为了简单起见,因为这不是问题的重点,在前面的例子中,我们认为 F
和 G
是函数对象,即 classes/structs 与 operator()
.
C++ 标准定义了术语转发引用。我想 universal reference 用作该术语的同义词。 [temp.deduct.call]/3
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template.
此概念仅适用于模板函数参数或模板构造函数参数。在所有其他情况下,T&&
是 右值引用 。 转发引用 的概念仅对模板参数推导有用。让我们考虑在以下示例中,所有函数和构造函数都使用 int
参数调用(独立于其常量和值类别 (lvalue/rvalue):
//possibilities of argument deduction, [cv] means any combination of "const" and "volatile":
// <"","const","volatile","const volatile">
template<class T> void f(T&);
//4 possibilities: void f([cv] int&);
template<class T> void f(const T&);
//2 possibilities: void f(const int&);
//void f(const volatile int&);
template<class T> void f(T&&);
//Forwarding reference, 8 possibilities
//void f([cv] int&);
//void f([cv] int&&);
template<class T> void f(const T&&);
//NOT a forwarding reference because of the const qualifier, 2 possibilities:
//void f(const int&&);
//void f(const volatile int&&);
template<class T>
struct S{
template<class U>
S(U&&);
//Forwarding reference, 8 posibilities:
//void S<X>([cv] int&);
//void S<X>([cv] int&&);
//no template argument deduction posible
S(T&&);
//NOT a forwarding reference, 1 possibility:
//void S<X>(X&&);
//Generated argument deduction:
//template<class T> S(T&&) -> S<T>;
//not a forwarding reference because T is a parameter of the template class;
//=> 4 possibilities: -> S<[cv] int&&>
T&& a; //an rvalue reference if T is [cv] int or [cv] int&&,
//an lvalue reference if T is [cv] int&;
//This comes from reference colapsing rules: &+&=&; &&+&=&; &&+&&=&& //(Nota: You may consider that a rvalue reference data member is probably a mistake)
};
template<class U>
S(U&&) -> S<U&&>;
//Forwarding reference, 8 possibilities:
// S<[cv] int&>;
// S<[cv] int&&>;
如果 std::forward
的参数可以是右值引用或左值引用,则使用 std::forward
仅在函数体内或构造函数内部有意义,具体取决于模板参数推导和引用崩溃的规则。如果 std::forward
的参数总是导致右值引用,则首选 std::move
,如果它总是导致左值引用,则不首选。