-2

This question follows on from How to pass by lambda in C++0x?, but perhaps this is a clearer way to ask the question.

Consider the following code:

#include <iostream>

#define LAMBDA(x) [&] { return x; }

class A
{
public:
  A() : i(42) {};
  A(const A& a) : i(a.i) { std::cout << "Copy " << std::endl; }
  A(A&& a) : i(a.i) { std::cout << "Move " << std::endl; }
  int i;
};

template <class T>
class B
{
public:
  B(const T& x_) : x(x_) {}
  B(T&& x_) : x(std::move(x_)) {}
  template <class LAMBDA_T>
  B(LAMBDA_T&& f, decltype(f())* dummy = 0) : x(f()) {}
  T x;
};

template <class T>
auto make_b_normal(T&& x) -> B<typename std::remove_const<typename std::remove_reference<T>::type>::type>
{
  return B<typename std::remove_const<typename std::remove_reference<T>::type>::type>(std::forward<T>(x));
}

template <class LAMBDA_T>
auto make_b_macro_f(LAMBDA_T&& f) -> B<decltype(f())>
{
  return B<decltype(f())>( std::forward<LAMBDA_T>(f) );
}

#define MAKE_B_MACRO(x) make_b_macro_f(LAMBDA(x))

int main()
{
  std::cout << "Using standard function call" << std::endl;
  auto y1 = make_b_normal(make_b_normal(make_b_normal(make_b_normal(A()))));
  std::cout << "Using macro" << std::endl;
  auto y2 = MAKE_B_MACRO(MAKE_B_MACRO(MAKE_B_MACRO(MAKE_B_MACRO(A()))));
  std::cout << "End" << std::endl;
}

Which outputs the following:

Using standard function call
Move 
Copy 
Copy 
Copy 
Using macro
End

It is clear that the macro version somehow elides all move/copy constructor calls, but the function version does not.

I assume that there is a syntactically nice way of eliding all the moves and copies without lots of macros in C++0x, but I can't figure it out.

Reason:

I plan to use such code without moves or copies for building parameter packs, i.e.

auto y = (_blah = "hello", _blah2 = 4);
f(y, (_blah3 = "world", _blah4 = 67));

And have these not have no extra moves/copies compared to non parameter code.

Community
  • 1
  • 1
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 3
    Or you might just pass by reference... – David Rodríguez - dribeas Jul 14 '11 at 11:55
  • 1
    What compiler are you using? I get 4 moves and 0 copies with GCC 4.6.1 – reko_t Jul 14 '11 at 12:04
  • @reko_t: That's with gcc 4.5.1 on ideone. However 4 moves is still more than no moves I get with the macro version. – Clinton Jul 14 '11 at 12:06
  • @David: How could I do that but avoid dangling references (as my macro version does)? – Clinton Jul 14 '11 at 12:06
  • 3
    @Clinton: The problem is that there is too much of your solution and too little of the actual problem. What is it that you want to achieve? – David Rodríguez - dribeas Jul 14 '11 at 12:31
  • @David: No moves using standard function calls just like there are no moves using the macro with the code given. – Clinton Jul 14 '11 at 12:37
  • 2
    @Clinton: Moves *should* be almost free, why do you feel the need to remove those few moves? Why do you want/need the extra indirections? What is the reason for the existence of `B` as a type? You are still focusing on a detail rather than the problem. And note that the test is not telling you all the truth: the lambda is being moved, you just don't see that in the output. Moving the lambda is a low cost operation, but so is moving in general (moving a vector is three pointer swaps, I don't think creation and moving of the lambda is going to be much cheaper) – David Rodríguez - dribeas Jul 14 '11 at 12:47
  • @David: Isn't moving types that don't use dynamic memory as expensive as copies (i.e std::array)? – Clinton Jul 14 '11 at 13:43
  • 4
    @Clinton: Yes they are, but you are adding a whole lot of complexity here for which I don't see any point. Note that my first comment was *pass by reference*, which is still true: If you cannot efficiently move, pass a reference and make a unique copy internally. That is what your *pass-by-lambda* is doing (which by the way could be *pass-by-std::ref* or any other version of it. Until you actually provide some insight into what you are trying to solve the question is pointless – David Rodríguez - dribeas Jul 14 '11 at 13:46
  • @Clinton: Yes, moving types that don't use internal references is still (almost) as expensive as copying them. However, the actual time spent in that kind of functionality is very low for most code, and the real performance problem is in types that do handle dynamic references. – Puppy Jul 14 '11 at 13:49
  • @David: Could you show some code which does pass by reference but doesn't have dangling reference issues? – Clinton Jul 14 '11 at 13:55
  • 2
    @Clinton: What is your definition of *dangling reference issues*? Just take a look at the STL (pre C++0x) and you will see a whole lot of code that passes by reference and *works*. Or download any C++ project from anywhere. What is the problem with *pass-by-reference*? – David Rodríguez - dribeas Jul 14 '11 at 14:00
  • @David: Using the standard function call I am passing by reference, and it is still doing moves. I don't see how I can pass by reference and elide the moves. – Clinton Jul 14 '11 at 23:49

1 Answers1

1

You failed to provide a move constructor for B. When that is provided, then on Visual Studio 2010 I get a bunch of moves. The reason that it doesn't ellide any of the moves is because you created a B<B<B<B<A>>>>.

Compilers are still very immature w.r.t. move ellision. I would expect them to get better at it later.

Puppy
  • 144,682
  • 38
  • 256
  • 465