Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forward variadic template args to several class members

Is the following safe? Won't the std::string be moved after the first class member is initialised? It prints out ok but I'm not sure.

template <typename T>
class Test
{
public:

    template <typename... Args>
    Test(Args&&... args)
    :  m_one(new T(std::forward<Args>(args)...)),
       m_two(new T(std::forward<Args>(args)...))    // <- Here
    {
    }

private:
    std::unique_ptr<T> m_one;
    std::unique_ptr<T> m_two;
};


class C
{ 
public:

    C(int a, int b, const std::string& c)
    :  m_a(a),
       m_b(b),
       m_c(c)
    {
        std::cout << "ctor a=" << m_a << ", b=" << m_b << ", c=" << m_c << "\n";
    }

    int m_a;
    int m_b;
    std::string m_c;
};


int main()
{
     Test<C> t(1, 2, "3");
}

I guess it's ok since the third ctor param of C is const std::string&, but how do I prevent perfect forwarding in a class that takes an r-value ref, e.g. C(int, int, std::string&&) as then m_two will not receive the same ctor args as m_one?

Changing the ctor of Test to

   template <typename... Args>
   Test(Args&... args)

doesn't compile. Nor does removing the std::forward<Args>(args)... from m_one and m_two ctors.

like image 442
James Avatar asked Mar 22 '23 11:03

James


1 Answers

You are going to want to use something like this:

#include <memory>
#include <string>
#include <iostream>
#include <utility>

template <typename T>
class Test
{
public:

    template <typename... Args>
    Test(Args&&... args)
    :  m_one(new T(args...)),                    // avoid moving the first time
       m_two(new T(std::forward<Args>(args)...)) // but allowing moving the last time
    {
    }

private:
    std::unique_ptr<T> m_one;
    std::unique_ptr<T> m_two;
};


class C
{
public:

    C(int a, int b, std::string c) // rule of thumb -- if you are going to copy an argument
                                   // anyway, pass it by value.
    :  m_a(a),
       m_b(b),
       m_c(std::move(c)) // you can safely move here since it is the last use. 
    {
        std::cout << "ctor a=" << m_a << ", b=" << m_b << ", c=" << m_c << "\n";
    }

    int m_a;
    int m_b;
    std::string m_c;
};

For m_one, the arguments use lvalue references, so no moving will take place. For m_two, the std::forward will use rvalue references as appropriate. Taking the std::string argument to C by value and using std::move makes it work properly for either case. If you pass an lvalue reference, then the argument will be copy-constructed, but if you pass an rvalue reference, the argument will be move-constructed. In either case, you can move the argument into your m_c member for efficiency.

like image 187
Vaughn Cato Avatar answered Apr 08 '23 13:04

Vaughn Cato