如何从就地临时变量初始化非静态私有模板成员变量,即不进行复制或移动?
How can I initialize non-static private template member variables from temporaries in-place, i.e., without making a copy or move?
我想从临时就地初始化 class 模板的两个非静态私有模板成员变量,即不进行复制或移动。
为了清楚起见,请考虑以下示例代码:
#include <iostream>
struct P {
P(int n) : n_ { n } {};
P(P&&) { std::cout << "P:moved" << std::endl; }
P(const P&) { std::cout << "P:copied" << std::endl; }
int n_;
};
struct Q {
Q(double x) : x_ { x } {};
Q(Q&&) { std::cout << "Q:moved" << std::endl; }
Q(const Q&) { std::cout << "Q:copied" << std::endl; }
double x_;
};
/* note that P and Q are just two illustrative examples;
don't count on anything specific in them; with respect
to the asked question, they should just represent two
arbitrary classes with arbitrary ctors */
template<typename U, typename V>
class X {
public:
X(U u, V v) : u_ { u }, v_ { v } {}
private:
U u_;
V v_;
};
int
main(
) {
X x { P { 0 }, Q { 0.0 } };
return 0;
}
输出(使用 gcc 8.2.0)是 P:copied Q:copied
,因为 u 和 v 分别被复制到 X 的构造函数中的 u_ 和 v_。但是,由于临时变量 P { 0 } 和 Q { 0.0 } 仅分别用于初始化 u_ 和 v_,我想知道是否可以就地初始化两个成员变量。我不想在这里看到 copied
和 moved
。更重要的是,我想 运行 这段代码删除了 P 和 Q 的复制和移动构造函数。
这在 C++17(或更早版本)中是否可行,如果可行,如何实现?
正如 plexando 在评论中已经指出的那样,我们无法做到既不移动也不复制。通过在 main 的主体(在调用站点)中不构造任何对象 P 或 Q 来省略一次复制,但是 X::X(U u, V v) 中的参数 u 和 v 必须有效,从中值被移动或复制。这个不能省略。
我们能做的最好的事情是让X::X使用通用引用然后转发,这将导致执行移动而不是复制。
X(U&& u, V&& v) : u_{ std::forward<U&&>(u) }, v_{ std::forward<V&&>(v) } {}
这幅画让我感动了两次。然而,还有另一个选项类似于 standard_container::emplace 中的参数被转发的方式。使用一些恶心的 std::enable_if 魔法,你可以编写这个构造函数
template<typename TO_U, typename TO_V, typename = std::enable_if_t<std::is_constructible_v<U, TO_U> && std::is_constructible_v<V, TO_V>>>
X(TO_U&& to_u, TO_V&& to_v) : u_(std::forward<TO_U&&>(to_u)), v_(std::forward<TO_V&&>(to_v)) {}
将任何施工推迟到最新时刻。在这种情况下,没有 moved 或 copied 被打印出来,如果数据成员 u_ 和 v_ 不能从传递的参数构造出来,它就会被 SFINAEd 删除。然而,无论它是否适用于您的问题,或者您的 类 太复杂而无法以这种方式构造它们,这对您来说都是有用的。
TLDR:如果你不能通过尽可能长时间地完美地形成参数来推迟构建,你将总是复制或移动,因为复制省略不会走到这一步。
基本上做你想做的事,你需要构建一种接口,std::pair
用来将成员的构造函数的参数转发给成员。他们这样做的方法是构建一个参数元组,然后将这些元组委托给另一个构造函数,该构造函数也获取每个元组参数包大小的 std::integer_sequence
,这样它就可以解包元组使用这些序列直接调用成员构造函数。下面的代码并不完美,但它会让您走上构建生产版本的道路。
template<typename U, typename V>
class X {
public:
// old constructor that makes copies
X(U u, V v) : u_ { u }, v_ { v } { std::cout << "X(U, V)\n"; }
// this is the constructor the user code will call
template<typename... Args1, typename... Args2>
X(std::piecewise_construct_t pc, std::tuple<Args1...>&& u, std::tuple<Args2...>&& v) :
X(pc, std::move(u), std::move(v), std::make_index_sequence<sizeof...(Args1)>{}, std::make_index_sequence<sizeof...(Args2)>{}) {}
// this is where the magic happens Now that we have Seq1 and Seq2 we can
// unpack the tuples into the constructor
template<typename... Args1, typename... Args2, auto... Seq1, auto... Seq2>
X(std::piecewise_construct_t pc, std::tuple<Args1...>&& u, std::tuple<Args2...>&& v, std::integer_sequence<size_t, Seq1...>, std::integer_sequence<size_t, Seq2...>) :
u_ { std::get<Seq1>(u)... }, v_ { std::get<Seq2>(v)... } {}
private:
U u_;
V v_;
};
int main()
{
// and now we build an `X` by saying we want the tuple overload and building the tuples
X<P,Q> x { std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(0.0) };
// Unfortunetly we don't get CTAD with this. Not sure if that can be fixed with a deduction guide
}
您还可以查看 libc++ 或 libstdc++ 等开源 C++ 库之一,了解它们如何实现 std::pair
的分段构造函数,以了解如何使其具有生产价值。
我想从临时就地初始化 class 模板的两个非静态私有模板成员变量,即不进行复制或移动。
为了清楚起见,请考虑以下示例代码:
#include <iostream>
struct P {
P(int n) : n_ { n } {};
P(P&&) { std::cout << "P:moved" << std::endl; }
P(const P&) { std::cout << "P:copied" << std::endl; }
int n_;
};
struct Q {
Q(double x) : x_ { x } {};
Q(Q&&) { std::cout << "Q:moved" << std::endl; }
Q(const Q&) { std::cout << "Q:copied" << std::endl; }
double x_;
};
/* note that P and Q are just two illustrative examples;
don't count on anything specific in them; with respect
to the asked question, they should just represent two
arbitrary classes with arbitrary ctors */
template<typename U, typename V>
class X {
public:
X(U u, V v) : u_ { u }, v_ { v } {}
private:
U u_;
V v_;
};
int
main(
) {
X x { P { 0 }, Q { 0.0 } };
return 0;
}
输出(使用 gcc 8.2.0)是 P:copied Q:copied
,因为 u 和 v 分别被复制到 X 的构造函数中的 u_ 和 v_。但是,由于临时变量 P { 0 } 和 Q { 0.0 } 仅分别用于初始化 u_ 和 v_,我想知道是否可以就地初始化两个成员变量。我不想在这里看到 copied
和 moved
。更重要的是,我想 运行 这段代码删除了 P 和 Q 的复制和移动构造函数。
这在 C++17(或更早版本)中是否可行,如果可行,如何实现?
正如 plexando 在评论中已经指出的那样,我们无法做到既不移动也不复制。通过在 main 的主体(在调用站点)中不构造任何对象 P 或 Q 来省略一次复制,但是 X::X(U u, V v) 中的参数 u 和 v 必须有效,从中值被移动或复制。这个不能省略。
我们能做的最好的事情是让X::X使用通用引用然后转发,这将导致执行移动而不是复制。
X(U&& u, V&& v) : u_{ std::forward<U&&>(u) }, v_{ std::forward<V&&>(v) } {}
这幅画让我感动了两次。然而,还有另一个选项类似于 standard_container::emplace 中的参数被转发的方式。使用一些恶心的 std::enable_if 魔法,你可以编写这个构造函数
template<typename TO_U, typename TO_V, typename = std::enable_if_t<std::is_constructible_v<U, TO_U> && std::is_constructible_v<V, TO_V>>>
X(TO_U&& to_u, TO_V&& to_v) : u_(std::forward<TO_U&&>(to_u)), v_(std::forward<TO_V&&>(to_v)) {}
将任何施工推迟到最新时刻。在这种情况下,没有 moved 或 copied 被打印出来,如果数据成员 u_ 和 v_ 不能从传递的参数构造出来,它就会被 SFINAEd 删除。然而,无论它是否适用于您的问题,或者您的 类 太复杂而无法以这种方式构造它们,这对您来说都是有用的。
TLDR:如果你不能通过尽可能长时间地完美地形成参数来推迟构建,你将总是复制或移动,因为复制省略不会走到这一步。
基本上做你想做的事,你需要构建一种接口,std::pair
用来将成员的构造函数的参数转发给成员。他们这样做的方法是构建一个参数元组,然后将这些元组委托给另一个构造函数,该构造函数也获取每个元组参数包大小的 std::integer_sequence
,这样它就可以解包元组使用这些序列直接调用成员构造函数。下面的代码并不完美,但它会让您走上构建生产版本的道路。
template<typename U, typename V>
class X {
public:
// old constructor that makes copies
X(U u, V v) : u_ { u }, v_ { v } { std::cout << "X(U, V)\n"; }
// this is the constructor the user code will call
template<typename... Args1, typename... Args2>
X(std::piecewise_construct_t pc, std::tuple<Args1...>&& u, std::tuple<Args2...>&& v) :
X(pc, std::move(u), std::move(v), std::make_index_sequence<sizeof...(Args1)>{}, std::make_index_sequence<sizeof...(Args2)>{}) {}
// this is where the magic happens Now that we have Seq1 and Seq2 we can
// unpack the tuples into the constructor
template<typename... Args1, typename... Args2, auto... Seq1, auto... Seq2>
X(std::piecewise_construct_t pc, std::tuple<Args1...>&& u, std::tuple<Args2...>&& v, std::integer_sequence<size_t, Seq1...>, std::integer_sequence<size_t, Seq2...>) :
u_ { std::get<Seq1>(u)... }, v_ { std::get<Seq2>(v)... } {}
private:
U u_;
V v_;
};
int main()
{
// and now we build an `X` by saying we want the tuple overload and building the tuples
X<P,Q> x { std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(0.0) };
// Unfortunetly we don't get CTAD with this. Not sure if that can be fixed with a deduction guide
}
您还可以查看 libc++ 或 libstdc++ 等开源 C++ 库之一,了解它们如何实现 std::pair
的分段构造函数,以了解如何使其具有生产价值。