Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ lambdas as class methods

As a hypothetical question, I would like to use lambdas as class methods. I understand this is bad in a professional context, but I'm curious anyway. An example would probably be best for showing what I want to do. Here is a basic class for complex numbers:

class Complex {
    private:
        double re, im;
    public:
        Complex() : re(0.0), im(0.0) {}
        Complex(double re, double im) : re(re * 1.0), im(im * 1.0) {}
        Complex(const Complex &c) = default;

        ~Complex() = default;

        function<double(void)> getRe = [=]() -> double { return re; };
        function<void(double)> setRe = [&](double re) -> void { this->re = re; };

        function<double(void)> getIm = [=]() -> double { return im; };
        function<void(double)> setIm = [&](double im) -> void { this->im = im; };

};

At first I have tried using auto instead of explicitly specifying function types but I got errors saying that I cant use auto in non static fields.

This seems to actually work, as in it apparently produces the desired behaviour. I have used it to draw some fractals using OpenGL so it ends up doing some fairly intensive work.

As I said it seems to work, I have used reference capture for the setters especially since I figured that since this is a reference to the current instance it might be needed, and value capture for the getters since by default an identifier ends up searching(in this case) in the class scope and finds the fields.

I have 2 questions:

  1. I have not tested this with visual studio but in what I am using, CLion with the MSVC compiler, the this is highlighted as as an inexistent variable(even though it "works"). Any ideas why this happens?

  2. The class ends up being slow. As in more than an order of magnitude slower. The rendering goes from being absolutely instant when I use plain getters and setters like double getRe() {return re;}, to taking 2-3 seconds. Why does this happen?

like image 680
Rares Dima Avatar asked Mar 07 '18 19:03

Rares Dima


People also ask

Can lambda be Constexpr?

Lambdas are now allowed inside constexpr functions.

Are lambdas copyable?

Copying a lambda will copy its state.

Can lambdas be templated?

From the various lambda improvements, template parameters for lambdas are my favorite ones. Lambdas support with C++20 template parameters, can be default-constructed and support copy-assignment, when they have no state, and can be used in unevaluated contexts.

Can lambdas be Inlined?

Calls of the lambda are translated to direct calls to its operator() and can therefore be inlined.


Video Answer


1 Answers

I like the idea, but it doesn't work out so well in practice.

std::function is a class type, like any other custom class you might write. sizeof(std::function) varies from implementation to implementation, but a reasonable value is 24 bytes1. This means that you'd be adding 24 bytes to sizeof(Complex) for every member std::function you want to add, compared to adding 0 bytes for every member function. Compared to sizeof(double) == 8 on most machines, that's a lot of overhead: your Complex type could be 16 bytes but is instead roughly 112 bytes.

Furthermore, every std::function member has to be initialized, possibly requiring a heap allocation, and calling a std::function involves virtual functions (or equivalent) because of type erasure. This makes it really hard for the compiler to optimize and nearly impossible for the compiler to inline the functions, whereas the regular member functions are almost guaranteed to be inlined due to how simple they are.

Using std::function for member functions means that your type is uselessly bigger, takes more work to initialize, and is much harder to optimize. That's why it's so much slower.

1: At this time, sizeof(std::function) is actually 32 bytes, 48 bytes, and 64 bytes on libstdc++, libc++, and MSVC's STL respectively


To avoid the per-object overhead, you could have static constexpr members (at least in C++17), but then you'd have to have an explicit this parameter, which removes all the nice sugar that member functions have. You'd have to write Complex::getRe(myComplex) rather than myComplex.getRe()

like image 144
Justin Avatar answered Oct 01 '22 16:10

Justin