3
\$\begingroup\$

I am working in a system that does not have room for an STL (think something like an Arduino), however I do like the comfort of std::function. For now I am only using this to wrap lambdas, but some sort of std::bind replacement is also on the roadmap. Important to know is that exception handling is not advised on said platform either and by not advised I mean the flag -fno-exceptions is enforced.

One of the things I would like to get some ideas about is how to do proper error handling in such a system. For example the operator () currently just returns a default created instance of the return type, if no callback has been given to the function. I feel like this is a bit ugly, however I would also like to avoid resorting to returning an error code. Are there other nice options?

Lets look at the usage first:

    function<void (bool)> _click_callback;
    function<void (ptrdiff_t)> _value_callback;

    // ...

    rot.click_callback([](bool clicked) {

    });

    rot.value_callback([](ptrdiff_t value) {

    });

and the actual code with all the "dependencies" like is_same. I'd love to hear some suggestions :)

template<typename T, typename R>
class is_same {
public:
    static constexpr bool value = false;
};

template<typename T>
class is_same<T, T> {
public:
    static constexpr bool value = true;
};

template<bool cond, typename type>
class enable_if {

};

template<typename type_name>
class enable_if<true, type_name> {
public:
    using type = type_name;
};

template<typename R, typename... Args>
class invoker {
public:
    virtual ~invoker() = default;

    virtual R invoke(Args... args) = 0;

    virtual invoker<R, Args...> *clone() = 0;
};

template<typename R, typename C, typename... Args>
class lambda_invoker : public invoker<R, Args...> {
    C _lambda;

public:
    explicit lambda_invoker(C lambda) : _lambda{lambda} {}

    R invoke(Args... args) override {
        return _lambda(args...);
    }

    invoker<R, Args...> *clone() override {
        return new lambda_invoker(_lambda);
    }
};

template<typename T>
class function_decayer : public function_decayer<decltype(&T::operator())> {

};

template<typename ClassType, typename ReturnType, typename... Args>
class function_decayer<ReturnType(ClassType::*)(Args...) const> {
public:
    using invoker = lambda_invoker<ReturnType, ClassType, Args...>;
};

template<typename T>
class function {

};

template<typename R, typename... Args>
class function<R(Args...)> {
public:
    using return_type = R;

private:
    invoker<R, Args...> *_invoker = nullptr;

public:
    function() = default;

    template<typename T>
    function(T invoker) {
        using decayer = function_decayer<T>;
        _invoker = new typename decayer::invoker(invoker);
    }

    function(const function &other) {
        if (other._invoker) {
            _invoker = other._invoker->clone();
        } else {
            _invoker = nullptr;
        }
    }

    function(function&& other) noexcept {
        _invoker = other._invoker;
        other._invoker = nullptr;
    }

    ~function() {
        delete _invoker;
        _invoker = nullptr;
    }

    function& operator = (const function& other) {
        if(this == &other) {
            return *this;
        }

        _invoker = other._invoker ? other._invoker->clone() : nullptr;
        return *this;
    }

    function& operator = (function&& other) noexcept {
        _invoker = other._invoker;
        other._invoker = nullptr;

        return *this;
    }

    template<typename ret = R>
    typename enable_if<is_same<ret, void>::value, ret>::type operator ()(Args... args) {
        if(!_invoker) {
            return;
        }

        _invoker->invoke(args...);
    }

    template<typename ret = R>
    typename enable_if<!is_same<ret, void>::value, ret>::type operator () (Args... args) {
        if(!_invoker) {
            return {};
        }

        return _invoker->invoke(args...);
    }
};
\$\endgroup\$
8
  • 3
    \$\begingroup\$ It might be good to add a few more details, such as is this an embedded system? There is a tag for that. Why the limitations? How do you handle memory allocation errors if exceptions are a bad idea? \$\endgroup\$
    – pacmaninbw
    Commented Feb 29, 2020 at 14:57
  • \$\begingroup\$ Yea, for the sake of it, lets assume its an embedded system. A fact is, that the compiler for the system enforces -fno-exceptions. Error handling would be one of the main things I'd like to get some inputs. \$\endgroup\$ Commented Feb 29, 2020 at 17:29
  • 1
    \$\begingroup\$ The fact is that someone not me, voted to close this question for lack of details so it would be good to add some details to the question. \$\endgroup\$
    – pacmaninbw
    Commented Feb 29, 2020 at 17:37
  • \$\begingroup\$ I have added that it should be considered something like an Arduino and that the no exceptions flag is mandatory by the compiler. What else could make sense to add? \$\endgroup\$ Commented Feb 29, 2020 at 17:39
  • 1
    \$\begingroup\$ You're still beating around the bush a lot and it reads like this is still not your real code. Please take a look at the FAQ on hypothetical code to understand why this is a problem. The deal is you give us a specific situation and we try to provide advice that fits the code. Generalizations lead to reviews that are not applicable to your actual situation and we have bad experiences with that. It's a waste of everyone's time. \$\endgroup\$
    – Mast
    Commented Mar 2, 2020 at 9:52

1 Answer 1

0
\$\begingroup\$

After using this for a while I have to say I did not improve anything, so I guess it is good enough.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.