Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to avoid copying lambda functor in this situation?

I made a finally simulator using lambda in C++11 as below:

#include <cstdio>

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor& func) : func_(func) {} // (1)
    ~Finalizer() { func_(); }

private:
    Functor func_; // (2)
};

template<typename functor>
Finalizer<functor> finally(functor& func)
{
    return Finalizer<functor>(func); (3)
}

int main()
{
    int a = 20;

    // print the value of a at the escape of the scope
    auto finalizer = finally([&]{ printf("%d\n", a); }); // (4)
}

The code works as intended, but there is undesired copy ctor call (of lambda functor) at the ctor of Finalizer struct (1). (Thankfully, copy construction at the return statement in the finally function (3 -> 4) is avoided by RVO.)

Compiler does not eliminate the copy ctor call (at least in vc10 - gcc may optimize it), and if the type of the functor in Finalizer struct (2) is changed to reference it'll crash since the lambda argument at the finally call (4) is r-value.

Of course the code can be "optimized" like below

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor& func) : func_(func) {}
    ~Finalizer() { func_(); }

private:
    Functor& func_;
};

int main()
{
    int a = 20;

    auto finalizer = [&]{ printf("%d\n", a); };
    Finalizer<decltype(finalizer)> fin(finalizer);
}

No overhead, only a printf call is placed at the end of scope. But... I don't like it. :( I tried to wrap it with macro, but it needs to declare two "name" - one for lambda object, the other for finalizer object.

My objective is simple -

  1. Every unnecessary performance overhead which can be avoided should be eliminated. Ideally, there should be no function call, every procedure should be inlined.
  2. Keep the concise expression as its purpose of utility function. Use of macro is allowed, but discouraged.

Is there any solution to avoid it for this situation?

like image 431
summerlight Avatar asked Oct 09 '22 23:10

summerlight


1 Answers

I presume lambdas have move constructors? If so, and if you will only ever use rvalues inside finally, then && and forward will move rather than copy.

#include <cstdio>

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor&& func) : func_(std::move(func)) {}
    Finalizer(Functor const& func) : func_(func) {} // (1)
    ~Finalizer() { func_(); }

private:
    Functor func_; // (2)
};

template<typename functor>
Finalizer<std::remove_reference<functor>::type> finally(functor&& func)
{
    return Finalizer<std::remove_reference<functor>::type>(std::forward<functor>(func)); // (3)
}

int main()
{
    int a = 20;

    // print the value of a at the escape of the scope
    auto finalizer = finally([&]{ printf("%d\n", a); }); // (4)
}

It should be possible to right something more intelligent that will work correctly with lvalues too, so that you're 'optimized' version will compile and will copy when it cannot move. In that case, I suggest you use something like Functor<std::remove_reference<functor>::type> to be sure that the Functor is of the right type, regardless of whether the parameters were passed around by & or && or whatever.

like image 170
4 revs, 2 users 97% Avatar answered Oct 13 '22 12:10

4 revs, 2 users 97%