5
\$\begingroup\$

I'm working on a C++ library for Arduino and other embedded systems.

I'm currently working on wrapping up function pointers and member-function pointers into two C++ template classes (function and mem_fn).

Does the following code abide by good programming practices? Does it provide enough functionality?

Since there seems to be some confusion about why I would need to write code like this, let me try to clear the air. 8-bit AVR microcontrollers don't have the same resources available to them that desktop computers do. Some of the constraints include limited RAM, limited program storage, limited compute capability, and limited feature support. Because of this, existing "standard" libraries are not well suited for embedded applications like this.

#ifndef kick_functional_h
#define kick_functional_h

//
// Copyright 2014 Kick project developers.
// See COPYRIGHT.txt or https://bitbucket.org/nwehr/kick/downloads/COPYRIGHT.txt
//
// This file is part of the Kick project and subject to license terms.
// See LICENSE.txt or https://bitbucket.org/nwehr/kick/downloads/LICENSE.txt
//

namespace kick {
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    template<typename ReturnT, typename... ArgT>
    class function {
        function();

    public:
        function( ReturnT (*)(ArgT...) );
        function( const function<ReturnT, ArgT...>& );

        function<ReturnT, ArgT...>& operator=( ReturnT (*)(ArgT...) );
        function<ReturnT, ArgT...>& operator=( const function<ReturnT,ArgT...>& );

        ReturnT operator()( ArgT... );

    protected:
        ReturnT (*_f)(ArgT...);
    };

    ///////////////////////////////////////////////////////////////////////////////
    // mem_fn
    ///////////////////////////////////////////////////////////////////////////////
    template<typename ObjectT, typename ReturnT, typename... ArgT>
    class mem_fn {
        mem_fn();

    public:
        mem_fn( ReturnT (ObjectT::*)(ArgT...) );
        mem_fn( const mem_fn<ObjectT, ReturnT, ArgT...>& );

        mem_fn<ObjectT, ReturnT, ArgT...>& operator=( ReturnT (ObjectT::*)(ArgT...) );
        mem_fn<ObjectT, ReturnT, ArgT...>& operator=( const mem_fn<ObjectT, ReturnT, ArgT...>& );

        ReturnT operator()( ObjectT&, ArgT... );

    protected:
        ReturnT (ObjectT::*_f)(ArgT...);
    };

}

///////////////////////////////////////////////////////////////////////////////
// function
///////////////////////////////////////////////////////////////////////////////
template<typename ReturnT, typename... ArgT>
kick::function<ReturnT,ArgT...>::function( ReturnT (*f)(ArgT...) )
: _f( f )
{}

template<typename ReturnT, typename... ArgT>
kick::function<ReturnT,ArgT...>::function( const kick::function<ReturnT, ArgT...>& f )
: _f( f._f )
{}

template<typename ReturnT, typename... ArgT>
kick::function<ReturnT, ArgT...>& kick::function<ReturnT, ArgT...>::operator=( ReturnT (*f)(ArgT...) )
{
    _f = f;
    return *this;
}

template<typename ReturnT, typename... ArgT>
kick::function<ReturnT, ArgT...>& kick::function<ReturnT, ArgT...>::operator=( const kick::function<ReturnT,ArgT...>& f )
{
    _f = f._f;
    return *this;
}

template<typename ReturnT, typename... ArgT>
ReturnT kick::function<ReturnT,ArgT...>::operator()( ArgT... a )
{
    return _f( a... );
}

///////////////////////////////////////////////////////////////////////////////
// mem_fn
///////////////////////////////////////////////////////////////////////////////
template<typename ObjectT, typename ReturnT, typename... ArgT>
kick::mem_fn<ObjectT,ReturnT,ArgT...>::mem_fn( ReturnT (ObjectT::*f)(ArgT...) )
: _f( f )
{}

template<typename ObjectT, typename ReturnT, typename... ArgT>
kick::mem_fn<ObjectT,ReturnT,ArgT...>::mem_fn( const mem_fn<ObjectT, ReturnT, ArgT...>& f )
: _f( f._f )
{}

template<typename ObjectT, typename ReturnT, typename... ArgT>
ReturnT kick::mem_fn<ObjectT,ReturnT,ArgT...>::operator()( ObjectT& o, ArgT... a ) {
    return (o.*_f)( a... );
}

template<typename ObjectT, typename ReturnT, typename... ArgT>
kick::mem_fn<ObjectT, ReturnT, ArgT...>& kick::mem_fn<ObjectT, ReturnT, ArgT...>::operator=( ReturnT (ObjectT::*f)(ArgT...) ) {
    _f = f;
    return *this;
}

template<typename ObjectT, typename ReturnT, typename... ArgT>
kick::mem_fn<ObjectT, ReturnT, ArgT...>& kick::mem_fn<ObjectT, ReturnT, ArgT...>::operator=( const mem_fn<ObjectT, ReturnT, ArgT...>& f ) {
    _f = f._f;
    return *this;
}

#endif
\$\endgroup\$
  • \$\begingroup\$ Why do you wrap function pointers? What is the wrapper supposed to do differently than regular function pointers? \$\endgroup\$ – nwp Nov 12 '14 at 20:15
  • \$\begingroup\$ Why are you not using the standard library? \$\endgroup\$ – Martin York Nov 12 '14 at 20:28
  • 1
    \$\begingroup\$ @Loki Astari: As mentioned, this is mainly targeted for the AVR mcu. There is no avrlibc++. \$\endgroup\$ – Nathan Wehr Nov 12 '14 at 20:48
  • 1
    \$\begingroup\$ Maybe something like uSTL is for you. Reimplementing the STL takes years and is just Yet Another STL Implementation. \$\endgroup\$ – nwp Nov 12 '14 at 21:29
  • 1
    \$\begingroup\$ Remember with headers the runtime footprint is only as large as the features you use. So you should really compare your code to similar code produced by std::function. Since std::function has been code reviewed debugged and vetted by thousands of people I bet it is actually smaller than any thing you can hand code. Now yes pulling in the C++ standard library is probably not a good idea; but using specific headers is something you should at least look at and verify before heading down the note invented here rode. \$\endgroup\$ – Martin York Nov 12 '14 at 23:19
2
\$\begingroup\$

Why

What problem do kick::function<R, Args...> and kick::mem_fn<C, R, Args...> solve? The former can be completely replaced by:

template <typename R, typename... Args>
using function = R(*)(Args...);

The latter on the one hand adds a better invoke mechanism, in that it's actually callable. But then it limits you to non-const member functions. But both can really be replaced by writing a reduced form of std::invoke:

template <typename R, typename... FArgs, typename... CArgs>
R invoke(R (*func)(FArgs...), CArgs&&... args) {
    return func(std::forward<CArgs>(args)...)
}

template <typename R, typename Cls, typename... FArgs, typename... CArgs>
R invoke(R (Cls::*func)(FArgs...), Cls& cls, CArgs&&... args) {
    return (cls.*func)(std::forward<CArgs>(args)...)
}

There's no SFINAE here (but you can add it easily). But this makes both of your class templates invokable the same way, so then there's really no need from the class templates.

As far as the code itself, the copy/move constructor/assignments can just be omitted since the compiler-generated ones will be correct. You can also omit the default mem_fn() constructor instead of making it private. The compiler will delete it for you.

Better

If you don't want to use std::function<Sig>, I suggest implementing your own. This will allow you to collapse both of your different function objects into one class template - which can then be agnostic to the use case.

struct C { void foo(); };
void bar(C& );

function<void(C&)> f1 = &C::foo;  // OK
function<void(C&)> f2 = bar;      // OK

One simple way to accomplish such type erasure is to have a placeholder type that you keep a pointer to, with a pure virtual function to call:

struct placeholder {  
    R call(Args...) = 0;
};

Which would then be provided by either a "any functor" holder, or a "pointer-to-member" holder, or a "pointer-to-const-member" holder, or "pointer-to-rvalue-ref-qualified-member" holder, or ... Point is, lots of partial specializations.

This has a use-case. Type erasure. Give me any callable that meets this signature. The original solution can't solve that problem.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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