-1

It seems the way to construct objects in C++0x avoiding copies/moves (particularly for large stack allocated objects) is "pass by lambda".

See the following code:

#include <iostream>

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

class A
{
public:
  A() {};
  A(const A&) { std::cout << "Copy "; }
  A(A&&) { std::cout << "Move "; }
};

class B1
{
public:
  B1(const A& a_) : a(a_) {}
  B1(A&& a_) : a(std::move(a_)) {}
  A a;
};

class B2
{
public:
  B2(const A& a_) : a(a_) {}
  B2(A&& a_) : a(std::move(a_)) {}
  template <class LAMBDA_T>
  B2(LAMBDA_T&& f, decltype(f())* dummy = 0) : a(f()) {}
  A a;
};

int main()
{
  A a;
  std::cout << "B1 b11(       a ): ";
  B1 b11(a);
  std::cout << std::endl;
  std::cout << "B2 b12(LAMBDA(a)): ";
  B2 b12(LAMBDA(a));
  std::cout << std::endl;
  std::cout << std::endl;

  std::cout << "B1 b21(       std::move(a) ): ";
  B1 b21(std::move(a));
  std::cout << std::endl;
  std::cout << "B2 b22(LAMBDA(std::move(a))): ";
  B2 b22(LAMBDA(std::move(a)));
  std::cout << std::endl;
  std::cout << std::endl;

  std::cout << "B1 b31((       A() )): "; 
  B1 b31((A())); 
  std::cout << std::endl;
  std::cout << "B2 b32((LAMBDA(A()))): ";
  B2 b32((LAMBDA(A()))); 
  std::cout << std::endl;
  std::cout << std::endl;
}

Which outputs the following:

B1 b11(       a ): Copy 
B2 b12(LAMBDA(a)): Copy 

B1 b21(       std::move(a) ): Move 
B2 b22(LAMBDA(std::move(a))): Move 

B1 b31((       A() )): Move 
B2 b32((LAMBDA(A()))): 

Note the "pass by lambda" removes the move in the case where the parameter is a what I believe is called a "prvalue".

Note that it seems the "pass by lambda" approach only helps when the parameter is a "prvalue", but it doesn't seem to hurt in other cases.

Is there anyway to get functions to accept "pass by lambda" parameters in C++0x, that is nicer than the client having to wrap their parameters in lambda functions themselves? (other than defining a proxy macro that calls the function).

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • One of your template parameter is `LAMBDA`, just like your macro. – Luc Danton Jul 14 '11 at 10:04
  • 2
    It's not very clear what you're trying to do. Why do you think this will avoid copies/moves? – Cory Nelson Jul 14 '11 at 10:06
  • @Luc: Sorry, I've updated the question to change the name of the template parameter. I don't think it made any difference though. – Clinton Jul 14 '11 at 10:09
  • @Cory: The "pass by lambda" avoids the move in the final case. – Clinton Jul 14 '11 at 10:10
  • What do you mean by "pass by lambda"? – jalf Jul 14 '11 at 10:22
  • There's a fundamental problem with this. You cannot magic an object into existence. Either the variable is default constructed, copy constructed, move constructed, or constructed with a different constructor. And since you only have the first 3 constructors, the last one isn't an option. Therefore, your "pass by lambda" mechanism doesn't seem to be passing _anything_ at all. If it's not move or copy constructed, then it's default constructed, and no information is being transferred to the new object. So while it may be faster, it isn't actually _doing anything_. – Nicol Bolas Jul 14 '11 at 10:44
  • http://ideone.com/eCqve I edited your empty default constructor to show what Nicol is talking about – KarlM Jul 14 '11 at 10:49
  • @KarlM: I'm not sure what that example shows. It still shows that the move is avoided in the lambda case. – Clinton Jul 14 '11 at 11:04
  • @Nicol: Can you show me any evidence that the pass by lambda mechanism isn't doing anything? Feel free to add data to A. – Clinton Jul 14 '11 at 11:04
  • Whether it is doing anything or not, the lambda itself does require overhead. All those captures take up space and have to be passed around, eating into the (likely marginal) savings you are getting from this mechanism. Plus the lambda is a whole new type, which likely results in a larger executable. – Dennis Zickefoose Jul 14 '11 at 15:58
  • @Clinton in the lambda case, the move is avoided because you are constructing an entirely new object - the default constructor is called instead of the move constructor – KarlM Jul 15 '11 at 23:07

2 Answers2

3

If you're okay with a templated constructor, you might as well use perfect forwarding instead of the obfuscation with lambdas.

class super_expensive_type {
public:
    struct token_t {} static constexpr token = token_t {};

    super_expensive_type(token_t);
}
constexpr super_expensive_type::token_t super_expensive_type::token;

class user {
public:
    template<typename... Args>
    explicit
    user(Args&&... args)
        : member { std::forward<Args>(args)... }
    {}

private:
    super_expensive_type member;
};

// ...

// only one construction here
user { super_expensive_type::token };

super_expensive_type moved_from = ...;
// one move
user { std::move(moved_from) };

super_expensive_type copied_from = ...;
// one copy
user { copied_from };

Using lambdas can't be better than this because the result from the expression in the lambda body has to be returned.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Sorry, I don't quite understand. Perhaps you could explain further, or give code that compiles? Thanks. – Clinton Jul 14 '11 at 10:27
  • @Clinton Fixed one bit. Sprinkle with `#include `, fill in the `...` blanks and put a `main` and it's compilable. But it's intended as pseudo-code, what do you want me to explain? – Luc Danton Jul 14 '11 at 10:38
  • I don't see how this elides the move. super_expensive_type doesn't have user defined move and copy constructors like A does so it's hard to see whether those moves/copies will be elided in types with custom move/copy constructors. – Clinton Jul 14 '11 at 11:02
  • @Clinton It does the same thing as using a lambda, but without the lambda. Writing `[&]() -> A&& { return std::move(a); }` caller-side and `member { f() }` callee-side can't be better than `std::move(a)` and `member { std::forward(args) ... }`. In both cases an rvalue reference (well, one xvalue) is used to initialize the member, which results in exactly one move construction. (Your way however returns `A`, not `A&&`, which is potentially one more move construction.) – Luc Danton Jul 14 '11 at 11:12
  • I don't understand how your version elides the move for prvalues. If it doesn't I don't understand how it is any better than the B1 class I initially mentioned as the base case. – Clinton Jul 14 '11 at 11:17
  • @Clinton It's only better in the sense that it avoids obfuscation. It's equivalent in power. – Luc Danton Jul 14 '11 at 11:30
  • @LucDanton let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1457/discussion-between-clinton-and-luc-danton) – Clinton Jul 14 '11 at 11:45
2

There's a fundamental problem with what you're doing. You cannot magic an object into existence. The variable must be:

  1. Default constructed
  2. Copy constructed
  3. Move constructed
  4. Constructed with a different constructor.

4 is off the table, since you only defined the first three. Your copy and move constructors both print things. Therefore, the only conclusion one can draw is that, if nothing is printed, the object is being default constructed. IE: filled with nothing.

In short, your Lambda-based transfer mechanism doesn't seem to be transferring anything at all.


After further analysis, I see what's happening. Your lambda isn't actually taking a value by reference; it's constructing a value. If you expand the macro, what you get is this:

B2 b32(([&] {return A()}));

It constructs a temporary; it doesn't actually take anything by reference. So I'm not sure how you can consider this "passing" anything. All you're doing is making a function that constructs an object. You could just as easily pass the arguments for B2::a's constructor to the constructor of B2 and have it use them to create the object, and it would give you the same effect.

You're not passing a value. You're making a function that will always create the exact same object. That's not very useful.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Sorry if I've got this wrong, but can't X x = func() use copy elision to put the result of func() directly into x without calling the copy constructor? And if so, why can't the copy from f() to a be elided? – Clinton Jul 14 '11 at 10:58
  • @Clinton: You can't use copy elision on a reference. And the value is stored by reference, not by value. You also can't use copy elision with constructor syntax (`A(f())`). – Nicol Bolas Jul 14 '11 at 11:13
  • So how does B get the data from a (as shown in ideone.com/2WUIe) without a move, copy or copy elision? – Clinton Jul 14 '11 at 11:18
  • @Clinton: See my edit. Also, `A` fills in its own data in is default constructor. That doesn't constitute passing something. – Nicol Bolas Jul 14 '11 at 11:19
  • To augment what Nicol has already said: Coding in this manner creates several extra objects and is both highly inefficient and horrendous to read. – Zac Howland Jul 14 '11 at 19:24