Efficient argument passing for c++11

If I have a vector class, I might have a push_back method like:

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
class Vector
{
    void push_back(const T & _val)
    {
        // Verify size of storage is sufficient here

        mStorage[size++] = _val;
    }

    // ...impl
}


From the client standpoint, if an rvalue is passed in, _val will bind to the rvalue, and then will be copied via the assignment operator, so 1 copy. If you pass in a const T & or T &, same thing - 1 copy. But imagine if we wrote push back like:

1
2
3
4
5
6
void push_back(T _val)
{
    // Verify size of storage is sufficient here

    mStorage[size++] = std::move(_val);
}


Now, if an rvalue is passed into push_back *and* T defines a move constructor, we can add the item to mStorage with 0 copies. If a const T & or T & is passed in, we get a single copy when constructing _val. But, if T does *not* define a move constructor, the latter case will cause 2 copies (a decrease in performance).

So the question is - for a template class like the above, what's the best way to take advantage of types of T that define a move constructor? Would something like the below give best results in all cases, assuming you are dealing with objects for which deep copies are expensive? (note: if anyone can reduce the below for me, that would also be appreciated - my template meta programming skills are a bit lacking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
template <typename T>
class Vector
{
public:   	
    void _push_back_cref(const T & _val)
    {
	cout << "const ref" << endl;
        new (mStorage + size++) T(_val); // Think that's the right syntax...
    }
	
    void _push_back_move(T && _val )
    {
	cout << "rvalue ref" << endl;
        new (mStorage + size++) T(move(_val)); // Think that's the right syntax...
    }
	
    template <typename T2, bool U>
    struct pusher
    {
		static void push_back(Vector<T2> & _vec, const T2 & _val)
		{
			_vec._push_back_cref(_val);
		}
    };

    template <typename T2>
    struct pusher<T2, true>
    {
		static void push_back(Vector<T2> & _vec, T2 _val)
		{
			_vec._push_back_move(move(_val));
		}
    };

    // ...impl
};

struct Square
{
	Square() { }
	// Square(Square && rref) = delete;  // uncomment to use _push_back_cref	
};

int main()
{
	Vector<Square> intVec;
	Vector<Square>::pusher<Square, is_move_constructible<Square>::value>::push_back(intVec, Square());
	cout << "Goodbye" << endl;	

    return 0;
}
Last edited on
if an rvalue is passed into push_back [...] if T does *not* define a move constructor, the latter case will cause 2 copies (a decrease in performance).

That copy is elided: when that rvalue is constructed, it is constructed directly inside the receiving function's stack frame. That's why it's been canon for operator= to take its argument by value long before move semantics.

Or, yes, you could provide a T&& overload, just like the real vector::push_back does.
The 2nd case above was passing a const T & or T & into push_back; the copy cannot be elided. I guess the other part of the equation I was thinking about is, while you can override when there is a single parameter, what about if it's 5 parameters, all of which may or may not supply move constructors. Would you override every possible combination of terms, giving 2^5 definitions? Or is there a way to get the best of both worlds? (the solution I gave isn't a solution for that at all, now that I think about it)
Last edited on
Would you override every possible combination of terms, giving 2^5 definitions?

I'd write a template function taking five T&&'s and use std::forward<Tn>(argN) inside the function body -- that's the reason std::forward was introduced.
Hmm, that makes sense. So just
1
2
3
4
5
template <typename U>
void push_back(U _val)
{
    new (mStorage + size++) T(forward(_val));
}


seems like it would handle all the cases...guess that's a bit simpler.
Topic archived. No new replies allowed.