HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Wrap function pointers in template classes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
templatefunctionpointersclasseswrap

Problem

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
class function {
function();

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

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

ReturnT operator()( ArgT... );

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

///////////////////////////////////////////////////////////////////////////////
// mem_fn
///////////////////////////////////////////////////////////////////////////////
template
class mem_fn {
mem_fn();

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

mem_fn& operator=( R

Solution

Why

What problem do kick::function and kick::mem_fn solve? The former can be completely replaced by:

template 
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 
R invoke(R (*func)(FArgs...), CArgs&&... args) {
    return func(std::forward(args)...)
}

template 
R invoke(R (Cls::*func)(FArgs...), Cls& cls, CArgs&&... args) {
    return (cls.*func)(std::forward(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, 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 f1 = &C::foo;  // OK
function 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.

Code Snippets

template <typename R, typename... Args>
using function = R(*)(Args...);
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)...)
}
struct C { void foo(); };
void bar(C& );

function<void(C&)> f1 = &C::foo;  // OK
function<void(C&)> f2 = bar;      // OK
struct placeholder {  
    R call(Args...) = 0;
};

Context

StackExchange Code Review Q#69632, answer score: 2

Revisions (0)

No revisions yet.