C++ template to convert function into function object type
Asked Answered
S

5

5

I'm trying to use std::unique_ptr with a custom deleter to simplify managing the life of handles returned to me from various C APIs. This is nice in theory, but I'm struggling to find an approach which is both optimal at runtime, and doesn't have a heap of boilerplate for every wrapped type.

For example, consider some opaque type foo which must be freed by passing its pointer to destroy_foo:

// approach 1: pass destroy_foo at runtime (bad for performance)
using foo_ptr = std::unique_ptr<foo, decltype(&destroy_foo)>;
foo_ptr bar{create_foo(...), destroy_foo};


// approach 2: make a deleter type (verbose - for many types)
struct foo_deleter
{
  void operator()(foo* p)
  {
    destroy_foo(p);
  }
};
using foo_ptr = std::unique_ptr<foo, foo_deleter>;
foo_ptr bar{create_foo(...)};

The first approach is hard for the compiler to optimize because I'm passing a function pointer around, so it's out. The second approach seems needlessly verbose. I have quite a lot of types like this I want to manage, and manually creating a class for each one is painful.

How can I define a class template that takes destroy_foo and gives me a type equivalent to foo_deleter? Or is there a standard library template to do this?

// best of both worlds - to_obj<Func> makes foo_deleter from destroy_foo...
using foo_ptr = std::unique_ptr<foo, to_obj<destroy_foo>>;
foo_ptr bar{create_foo(..)};

So given any function, the template would define a class with an operator() which simply forwards all arguments to the function, and returns the result.

Sadfaced answered 21/3, 2014 at 7:56 Comment(2)
Does the performance of an indirect call from a stored pointer really matters when we consider that it is for processing the termination of a resource ( freeing memory, closing file handle,… ) ?Acquittance
Yes, they are not always heavy-weight resources. Aside from the extra CPU time, there's the fact that the size of every pointer will potentially double.Sadfaced
D
5

Something like

template<typename T, void (*func)(T*)>
struct Deleter{
  void operator()(T* t) { func(t); }
};

??

Or if you want something a bit more robust

template <typename t>
struct function_traits;

template <typename R, typename A>
struct function_traits<R (*)(A)>
{
   using t_ret = R;
  using t_arg = A;
};

template <typename F, F* f>
struct Functor
{
  using FT = function_traits<F*>;
  typename FT::t_ret operator()(typename FT::t_arg a) { 
    return f(a);
   }
};


void mydeleter(int*);
#define FUNCTOR(F) Functor<decltype(F),&F>

Or using the full might of C++11

template <typename F, F* f>
struct Functor
{
  template<typename... A>
  auto operator()(A&&... a) -> decltype(f(std::forward<A>(a)...)) {
    return f(std::forward<A>(a)...);
   }
};

#define FUNCTOR(F) Functor<decltype(F),&F>
Dreiser answered 21/3, 2014 at 8:43 Comment(4)
I was thinking down these lines, but sometimes the return type of the function may not be void. It's unfortunate that to use it will require writing the 'T' type multiple times too. ie: std::unique_ptr<foo, Deleter<foo, delete_foo>> - but perhaps this is as good as it can get!Sadfaced
OK this is the simplest variant. You can pass the function type to the template, and extract the return type and the argument type inside, so you will have something like Deleter<decltype(delete_foo),delete_foo>. Then you can simplify it even more with a macro, if you fancy such things.Dreiser
The function traits are really interesting, but I just realised there is a way to avoid them. Keep your struct Functor but change operator() to be a variadic template function that forwards all its arguments to f. ie: template <typename... A> auto operator()(A&&... a) -> decltype(f(std::forward<A>(a)...)) { return f(std::forward<A>(a)...); } won't this work just as well?Sadfaced
@Sadfaced Sure you can do that as well, I will update the answer.Dreiser
Z
2

In C++17:

template <auto F>
struct Functor
{
    template <typename... Args>
    auto operator()(Args&&... args) const { return std::invoke(F, std::forward<Args>(args)...); }
};

allows:

std::unique_ptr<char, Functor<printf>>(new char[50]{ "Hello Template Metaprogramming World!" });
Zambia answered 10/1, 2019 at 2:15 Comment(2)
For those wondering, this takes in the function as a function pointer and invokes it inside the function call operator. auto F non-type parameter template, introduced by C++17, is the same as <typename T, T f> in previous versions. Syntax sugar but makes the code more readable.Morey
This approach gets inlined even in lower optimzation settings: live example.Morey
K
0

If I understand your question correctly, you're looking for something like this?

#include <iostream>
#include <memory>

template <typename TYPE>
void custom_deletor(TYPE * t)
{
    delete t;
}

template <typename TYPE>
struct delete_functor
{
    void operator () (TYPE * o)
    {
        custom_deletor(o);
    }
};

template <typename TYPE>
std::unique_ptr<TYPE, delete_functor<TYPE>> make_unique(TYPE * data)
{
    return std::unique_ptr<TYPE, delete_functor<TYPE>>(data, delete_functor<TYPE>());
}


struct foo
{
    static void special_delete(foo * bar)
    {
        printf("BYE BYE \n");
        delete bar;
    }
};

template <> void custom_deletor<foo>(foo * bar)
{
    foo::special_delete(bar);
}

int main(int argc, const char * argv[])
{
    auto v = make_unique(new foo);

    return 0;
}
Kerrin answered 21/3, 2014 at 8:34 Comment(1)
Thanks for the answer, but unfortunately this doesn't solve the problem. The verbosity just shifts into a template specialization. Having to provide a specialization for every type is just as bad as having to define the small class I gave in the example. It's probably a bit more confusing to read too.Sadfaced
P
0

You can also use lambdas as a short hand syntax for creating the deleter functor.

auto foo_deleter = [](foo *ptr){ destroy_foo(ptr); };
std::unique_ptr<foo, decltype(foo_deleter)> foo_ptr(create_foo(), foo_deleter);
static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");

In C++20 you can leave out the deleter parameter, making it more obvious that it is not stored.

Yet another idea is to overload std::default_delete<> for foo:

// In header:
template<>
struct std::default_delete<foo> {
    void operator()(foo *f) const { destroy_foo(f); };
};

// Usage:
std::unique_ptr<foo> foo_ptr(create_foo());
static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");

But this is probably a bad idea and surprising to readers. Make sure the overloaded std::default_delete is in scope, or it will use the standard delete.. Also, you can no longer create a smart pointer to a foo type that is managing heap memory.

Panjabi answered 6/12, 2019 at 10:19 Comment(0)
R
0

What you need to do is use the facilities of c++11 function adaptors std::bind or std::mem_fn, which convert a function to a function object.

Here's a demonstration.

#include <iostream>
#include <string>
#include <functional>
#include <typeinfo>

int myFun( const std::string& s, int i )
{
    std::cout << s << '\n';
    return i * i;
}

template<typename Func, typename... TArgs>
decltype(auto) toFunctor( Func f, TArgs&&... args )
{
    return std::bind( f, std::forward<TArgs>( args )... );
}

int main()
{
    using namespace std::string_literals;
    std::cout << "Func\n"s;
    std::cout << typeid( myFun( "hello"s, 5 ) ).name() << '\n';
    std::cout << "Function object\n"s;
    auto f = toFunctor( myFun, "hello"s, 5 );
    std::cout << typeid( f ).name() << '\n';
    f();
}

Output:

Func
int
Function object
class std::_Binder<struct std::_Unforced,int (__cdecl*& __ptr64)(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const & __ptr64,int),class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
hello
Rat answered 21/7, 2021 at 16:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.