Comment by cherryteastain

Comment by cherryteastain 6 months ago

4 replies

Why not just pass around an

    std::pair<void(*)(FuncData*), std;:unique_ptr<FuncData>>
at this stage? This implementation has a bunch of performance and ergonomics issues due to things like not using perfect forwarding for the Func1::Call(T) method, so for anything requiring copying or allocating it'll be a decent bit slower and you'll also be unable to pass anything that's noncopyable like an std::unique_ptr.
kjksf 6 months ago

I don't know fancy C++ so I don't understand your point about perfect forwarding.

But I do know the code I write and you're wrong about performance of Func0 and Func1. Those are 2 machine words and all it takes to construct them or copy them is to set those 2 fields.

There's just no way to make it faster than that, both at runtime or at compile time.

The whole point of this implementation was giving up fancy features of std::function in exchange for code that is small, fast (both runtime and at compilation time) and one that I 100% understand in a way I'll never understand std::function.

  • cherryteastain 6 months ago

    In this function

        void Call(T arg) const {
            if (fn) {
                fn(userData, arg);
            }
        }
    
    Say you pass something like an std::vector<double> of size 1 million into Call. It'll first copy the std::vector<double> at the point you invoke Call, even if you never call fn. Then, if fn is not nullptr, you'll then copy the same vector once more to invoke fn. If you change Call instead to

        void Call(T&& arg) const {
            if (fn) {
                fn(userData, std::forward<T>(arg));
            }
        } 
    
    the copy will not happen at the point Call is invoked. Additionally, if arg is an rvalue, fn will be called by moving instead of copying. Makes a big difference for something like

        std::vector<double> foo();
        void bar(Func1<std::vector<double>> f) {
            auto v = foo();
            f(std::move(v));
        }
  • OskarS 6 months ago

    > But I do know the code I write and you're wrong about performance of Func0 and Func1. Those are 2 machine words and all it takes to construct them or copy them is to set those 2 fields.

    You also have to heap allocate your userData, which is something std::function<> avoids (in all standard implementations) if it’s small enough (this is why the sizeof() of std::function is larger than 16 bytes, so that it can optionally store the data inline, similar to the small string optimization). The cost of that heap allocation is not insignificant.

    If I were doing this, I might just go the full C route and just use function pointers and an extra ”userData” argument. This seems like an awkward ”middle ground” between C and C++.

badmintonbaseba 6 months ago

Just use std::function, you don't have to pass a lambda. Any callable is fine.