0

Consider this example:

struct A {
    int val;
    A(int val) : val(val){};

    A operator+(const A& a1) {
        return (+[](const A& a1, const A& a2) -> A {
            return A(a1.val + a2.val);
        })(*this, a1);
    };
};

int main(int argc, char* argv[]) {
    A a(14);
    A b(12);
    auto c = a + b;

    return 0;
}

I have two questions:

  1. Will the compiler optimize-out the use of lambda in this case (which is just wrapper into the operator+ or will there be any overhead?
  2. Is there any simpler syntax for writing operators using lambda functions?
7
  • I fail to understand why you are using a lambda at all. Just return A(val + a1.val); Commented Jan 28, 2016 at 14:09
  • Why don't you just check: godbolt.org/g/2Zmo0p Commented Jan 28, 2016 at 14:10
  • 1
    There is no specific reason apart from the fact that I want to know the boundaries of lambdas in C++. I would never write such code in production. Commented Jan 28, 2016 at 14:11
  • I'd agree with @RamboRamon that this is an inappropriate use of a lambda. Commented Jan 28, 2016 at 14:11
  • Or a more valid solution: godbolt.org/g/9Ak2JR Commented Jan 28, 2016 at 14:11

1 Answer 1

9

Some parts of your code seem pointless. I will go through them and strip them out one-by-one.

A operator+(const A& a1) {
    return (+[](const A& a1, const A& a2) -> A {
        return A(a1.val + a2.val);
    })(*this, a1);
};

First, +[] simply decays it needlessly to a function pointer. Which can do nothing useful here, except possibly confuse the optimizer. We can also get rid of ()s:

A operator+(const A& a1) {
    return [](const A& a1, const A& a2) -> A {
        return A(a1.val + a2.val);
    }(*this, a1);
};

now, the ->A part is noise, as it can deduce the return type:

A operator+(const A& a1) {
    return [](const A& a1, const A& a2) {
        return A(a1.val + a2.val);
    }(*this, a1);
};

Next, why use member operator+? + is symmetric, and by using a Koenig-style operator this becomes more obvious:

friend A operator+(const A& a1, const A& a2) {
    return [](const A& a1, const A& a2) {
        return A(a1.val + a2.val);
    }(a1, a2);
};

which gets rid of the numbering confusion you introduced (where a1 in one scope is a2 in another, and a new variable called a1 is introduced that refers to *this).

Finally, the lambda does nothing at this point:

friend A operator+(const A& a1, const A& a2) {
  return A(a1.val + a2.val);
};

and deleting it results in clearer code.


Now about your question.

A compiler is more likely to optimize out the lambda as you go through these simplification steps. I suspect the worst thing you did was the unary +, which converted the lambda to a function pointer: I know gcc is good at inlining function pointers, and last I checked MSVC was bad at it. Every compiler is good at inlining a call to a stateless lambda, however.

At the point of call, the method being invoked is well known to the compiler by the type system, so no analysis of the providence of a function pointer has to be done. Data is copied to arguments then used, often in a small function. This is easy to inline.

Even if you have modest capture requirements, so long as you don't type erase or copy the lambda around, you have a bunch of local references or copies of local variables that are used in the body of the lambda. Easy to inline.

Now, each simplifying step in the first part of my answer makes the code simpler, until the end, when the lambda is gone and the only thing to "optimize" is a simple RVO (which you have to suppress with compiler flags to prevent it from happening), aka eliding the temporary A in the return statement.

If your A has an implicit constructor from the result of .val+.val, you don't even need that A in the return statement.


There isn't a simple way to write operators in terms of lambdas. You'll need to store said lambda somewhere, then glue the operator+ (or whatever) to it, those two steps are going to require more code than just injecting the body directly into the operator+.

I could do some fancy decltype and macro nonsense to let you bind a global lambda into being the body of a given operator+ for some class (using CRTP and a traits class), but calling that "simpler" is more than a bit of a stretch.

It would let you do something like:

const auto print_via_to_string = [](std::ostream& os, auto&& val){
  using std::to_string;
  os << to_string(decltype(val)(val));
};

struct Foo { /* ... */ };

BIND_OPERATOR( <<, std::ostream&, Foo const&, print_via_to_string );

or somesuch, and even expect the << to be inlined.

Again, considering this "simpler" is a stretch.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.