如何初始化非静态模板成员变量从临时工开始,即而无需复制或移动

How can I initialize non-static private template member variables from temporaries in-place, i.e., without making a copy or move?

本文关键字:开始 移动 复制 临时工 初始化 静态 变量 成员      更新时间:2023-10-16

我想初始化两个非静态私有模板成员变量,即从临时的就地开始,即不进行副本或移动。

要澄清,请考虑以下示例代码:

#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的CTOR中复制到U_和V_。但是,由于临时p {0}和q {0.0}仅用于初始化u_和v_,所以我想知道是否可以在就位初始化两个成员变量。我想在这里看到copiedmoved。更重要的是,我想使用p和q删除的复制和移动CTOR运行此代码。

这在C 17(或更早(中是否可以,如果是,则如何?

基本上要做您想做的事情,您需要构建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 之类的OpenSource C 库之一,以查看它们如何实现std::pair的分段构造函数,以获取如何使其值得。

>

正如Plexando在评论中已经指出的那样,我们既不能移动也不能复制。通过不在Main正文中构造任何对象P或Q(在呼叫站点上(,但是X :: X :: X(U 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)) {}

哪些结构都推迟到最新时刻。在这种情况下,没有移动的移动已复制打印,如果数据成员u_和v_无法从传递的参数构造,则它将被删除。但是,这对您来说是UT,无论是适用于您的问题还是您的课程太复杂,无法以这种方式构造它们。

tldr:如果您无法通过尽可能长时间完美地构建参数推迟构造,则您将始终复制或移动,因为复制Elision不会到达远处。