2

I have a library of C++11 code that I need to build on an older compiler that has no support for lambdas. It is not practical to manually change all the lambdas into hand-crafted function-objects.

Does anyone know of a tool that I can run as a precompilation step, that will automatically extract all the lambdas into their equivalent function-classes? I was wondering if I could use clang's front end perhaps.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
gimmeamilk
  • 2,016
  • 5
  • 24
  • 36
  • Function classes are not lambda equivalents, it's not possible. – Patryk Obara Feb 06 '15 at 13:18
  • Are you sure that is it just Lambdas? There is a lot more than that in C++11 that could give you a hard time there. Are there any C++11 things in the public interface? If not, maybe you can use a modern compiler to build the lib for your target platform and then just use the pre-built lib with your project in the old compiler. – learnvst Feb 06 '15 at 13:18
  • 4
    @PatrykObara: Yes they are. A lambda expression is a shorthand for declaring and instantiating a function class, with a data member for each capture and an `operator()` to invoke the lambda body. – Mike Seymour Feb 06 '15 at 13:49
  • @MikeSeymour No, they aren't - you can test it by comparing typeid of lambda expression and function object. Lambda expressions are now language feature, not syntax sugar. Tool, that converts lambdas to function classes is impossible to run pre-compilation, because it would depend on template instantiation and deduction of return type. – Patryk Obara Feb 06 '15 at 14:28
  • 4
    @PatrykObara: Of course it has a different type to any particular user-defined function class. But it's still a function class, as I described, and as specified by [expr.prim.lambda]. (You're right that return type deduction for non-lambda functions wasn't supported in C++11, so you'd either need to generate code for C++14 or later, or figure it out by analysing the lambda body during conversion.) – Mike Seymour Feb 06 '15 at 14:33
  • 1
    @MikeSeymour: Well, there is one thing that C++ lambdas can do that function objects can't: Capture the address of an entire stack frame and use the compiler's knowledge of its layout to access all the locals through that single pointer. But this is merely an implementation detail, although it cannot be duplicated in pure C++ code, there exists some implementation of the same lambda that can (which you have described -- separate data member for each captured variable). – Ben Voigt Feb 06 '15 at 17:12
  • @MikeSeymour I experimented a bit and it turns out we were both partly wrong, partly right :) Equivalence between lambas and function objects is actually implementation defined - see my answer below. – Patryk Obara Feb 06 '15 at 17:18
  • @BenVoigt: Yes, I suppose that optimisation is possible with a lambda but not a general function class; but the behaviour can be preserved. I was just taking issue with the claims that it's not a function class, and that it's not possible. – Mike Seymour Feb 06 '15 at 17:18
  • 1
    @PatrykObara: It's still specified to be a class type, and to behave as if it has a function call operator; therefore, it is a function class. – Mike Seymour Feb 06 '15 at 17:23
  • @BenVoigt: "Capture the address of an entire stack frame and use the compiler's knowledge of its layout to access all the locals through that single pointer." Nope. It does nothing with stack frames. A lambda works *exactly* the same as a class that has fields corresponding to each captured variable in the capture list (if the variable has type `T`, then the field has type `T` or `T&` depending on if it's specified to be by-value or by-reference capture, respectively) and initializing each field with the corresponding variable in the constructor of the class. – newacct Feb 06 '15 at 23:45
  • 1
    I've read the rules several times and they are very careful to allow the implementation I outlined (only for capture by reference of course) – Ben Voigt Feb 07 '15 at 00:23

2 Answers2

2

Does anyone know of a tool that I can run as a precompilation step, that will automatically extract all the lambdas into their equivalent function-classes?

Well, such tool might theoretically exist, but even if it did it would be more practical to simply switch to newer compiler in the first place.

Let's consider following code:

template <class F>
auto apply_0(F f) -> decltype(f(0)) {
    auto g = [f] (int x) { return f(x); };
    return g(0);
}

In this case, our tool would need to either generate function object, that returns auto type (which implies, that generated code would need to be compilable using C++14) or instantiate template to detect all possible return types, that appear in code (at this point it would be easier to switch to C++14).

But, let's consider even most optimistic situation: all lambda expressions throughout project define their return types using -> and they don't appear in template functions - in this case theoretically you could convert lambda expression to equivalent struct with operator() and constructor, that captures variables as struct members - would it give you code, that behaves the same way? Answer is: it's implementation-defined.

Consider even smallest possible lambda:

auto f = [] { return 0; };
cout << is_pod<decltype(f)>::value << endl;

When compiled with g++ - this code will write '0', with clang++ - it will write '1'. If we converted this lambda to function object - resulting struct would be POD type in both gcc and clang. Another example:

int x = 42;
auto f = [x] { return x; };
cout << is_pod<decltype(f)>::value << endl;

g++ will still give '0', clang++ will still give '1'. During conversion to struct we would need to write some initializer (constructor or other) for binded member x of our function object - depending on implementation it could be POD or non-POD, either way - it may be different than what compiler does when creating anonymous closure object for lambda.

Other things, that may change are listed by C++11 standard, § 5.1.2.3:

An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:

  • the size and/or alignment of the closure type,
  • whether the closure type is trivially copyable (Clause 9),
  • whether the closure type is a standard-layout class (Clause 9), or
  • whether the closure type is a POD class (Clause 9).

As always, your code shouldn't depend on implementation-defined parts of language - so this is not such a big deal, but it may lead to unexpected consequences nonetheless.

Patryk Obara
  • 1,847
  • 14
  • 19
-1

There isn't such a tool already built as far as I know, but if you were to try to implement one, your best choices of infrastructure would be Clang and ROSE.

Phil Miller
  • 36,389
  • 13
  • 67
  • 90