C++ Callable wrapper

Callable

Recently I was playing around with templates, parameter packs, etc. I was curious if it is possible to make a templated wrapper for a function pointer and then process it in some way.

As it turns out, it is pretty simple, when you get started. Keep in mind, that I'll be using the c++17 Microsoft compiler, but any c++17 compiler should work.
template <typename ReturnType, typename... Params>
class callable
{
private:
 ReturnType (*_f)(Params...);
 using FunctionAlias = ReturnType(*)(Params...);
 static ReturnType _f_empty(Params... args){}
public:
 callable() : _f(&_f_empty)
 {

 }
 callable(ReturnType (*f)(Params...)) : _f(f)
 {

 }

 callable &operator =(ReturnType(*f)(Params...))
 {
  _f = f;
  return *this;
 }

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

  _f = other._f;
  return *this;
 }

 ReturnType operator ()(Params... args)
 {
  if constexpr (std::is_same<ReturnType, void>::value)
  {
   _f(args...);
  }
  else
  {
   return _f(args...);
  }
 }
  
 static constexpr std::size_t parameter_count = sizeof...(Params);

 template <bool IsStatic, typename Checker>
 constexpr typename std::conditional<IsStatic, void, bool>::type check()
 {
  if constexpr (IsStatic)
  {
   static_assert(Checker::template check<ReturnType, Params...>(), "Specified function does not meet Checker's demands");
  }
  else
  {
   return Checker::template check<ReturnType, Params...>();
  }
 }

 typename std::add_pointer<ReturnType(Params...)>::type get()
 {
  return _f;
 }
};

As you can see, it has an empty constructor if template parameters are specified, a constructor from a function pointer and assignment operators from function pointer and callable itself.

I added parameter_count variable just in case I need to check it or use it in subsequent code and a get function to retrieve the pointer.

A wrapper for a function pointer has to contain a caller method or a getter to the function pointer, otherwise there is no point in wrapping altogether. Based on this, I made callable into a functor with the parenthesis operator. Giving it our parameter pack makes it difficult to traverse it inside the operator overload, but we only need to pass them to the function itself.

Additionally, notice the constexpr if to define whether our wrapped function has return type of void or not. I may not have been careful enough with constexpr throughout the code, but I made sure to include it in a few important places. Besides, I hope that the c++17 compiler can notice itself that we are using compile-time constants and optimize everything.

Now, the main focus of this tutorial is the check function. I made it in two variants - static and "runtime". It is managed by the IsStatic template parameter and uses an std::conditional to define the type with a constexpr if for the actual check.

Checkers

What does the Checker template parameter do? Well, it is a whole other story.
namespace checkers
{
 //Empty checker, always returns true
 struct checker_none
 {
  template <typename ReturnType, typename... Params>
  static constexpr bool check()
  {
   return true;
  }
 };
 //Checks if parameters match in type with return type
 struct checker_match_ret
 {
 private:
  //Dummy class in case of partial template specialization
  template <typename ReturnType, typename... Params>
  class wrapper
  {
  public:
   static constexpr bool value =
    std::conditional<
     sizeof...(Params) == 0, 
     std::is_same<ReturnType, void>, 
     detail::areT<ReturnType, Params...>
    >::type::value;
  };
 public:
  template <typename ReturnType, typename... Params>
  static constexpr bool check()
  {
   return wrapper<ReturnType, Params...>::value;
  }
 };
}

Our cool checker uses static const bool check() to find if our types match some requirements.

First, I made a simple checker_none that always returns true. I used it for training and as an example for someone, who wants to write their own checker. Then I wrote checker_match_ret, that checks if all parameters match the return type. You will notice detail::areT here, it is a modification of this post from stackoverflow to take a templated input parameter of the return type and not hardcode it.

Tests

Now it is time to actually use it. My example usage may not be very profound, but it should (hopefully) show you, why my callable is powerful. Maybe you will figure out your own example useges too!
#include <iostream>
#include "callable.h"

int add(int a, int b)
{
 return a + b;
}

void print(int a, int b)
{
 std::cout << a << " " << b << std::endl;
}

int getter()
{
 return 5;
}

void empty()
{
 std::cout << "empty" << std::endl;
}

template <typename ReturnType, typename... Params>
class accepts_template
{
private:
 features::callable<ReturnType, Params...> _f;
 struct checker_non_void
 {
  template <typename ReturnType, typename... Params>
  static constexpr bool check()
  {
   return !std::is_same<ReturnType, void>::value;
  }
 };
public:
 accepts_template() = delete;
 accepts_template(features::callable<ReturnType, Params...> f) : _f(f)
 {
  _f.check<true, features::checkers::checker_match_ret>();
  _f.check<true, checker_non_void>();
 }
 ReturnType call(Params... bs)
 {
  return _f(bs...);
 }
};

int main()
{
 std::cout << std::boolalpha;
 std::cout << "Check if all functions can be called:" << std::endl;

 features::callable my_callable1(add);
 std::cout << "call int add(int,int):    " << my_callable1(1,2) << std::endl;
 features::callable my_callable2(print);
 std::cout << "call void print(int,int): ";
 my_callable2(1, 2);
 features::callable my_callable3(getter);
 std::cout << "call int getter():        " << my_callable3() << std::endl;
 features::callable my_callable4(empty);
 std::cout << "call void empty():        ";
 my_callable4();

 std::cout << std::endl << "Check if functions meet demands:" << std::endl;
 
 std::cout << "void empty()        none:      " << my_callable4.check<std::false_type::value, features::checkers::checker_none>() << std::endl;

 std::cout << "int add(int,int)    match_ret: " << my_callable1.check<std::false_type::value, features::checkers::checker_match_ret>() << std::endl;
 std::cout << "void print(int,int) match_ret: " << my_callable2.check<std::false_type::value, features::checkers::checker_match_ret>() << std::endl;
 std::cout << "int getter()        match_ret: " << my_callable3.check<std::false_type::value, features::checkers::checker_match_ret>() << std::endl;
 std::cout << "void empty()        match_ret: " << my_callable4.check<std::false_type::value, features::checkers::checker_match_ret>() << std::endl;

 std::cout << std::endl << "Check data:" << std::endl;

 std::cout << "sizeof callable:     " << sizeof(my_callable1) << std::endl;
 std::cout << "sizeof checker:      " << sizeof(features::checkers::checker_match_ret) << std::endl;

 std::cout << "callable1 is source: " << (my_callable1.get() == &add) << std::endl;
 std::cout << "callable2 is source: " << (my_callable2.get() == &print) << std::endl;
 std::cout << "callable3 is source: " << (my_callable3.get() == &getter) << std::endl;
 std::cout << "callable4 is source: " << (my_callable4.get() == &empty) << std::endl;

 std::cout << std::endl << "Use accepts_template:" << std::endl;

 accepts_template acc1(my_callable1); //Will pass
 //accepts_template acc2(my_callable2); //Will fail
 std::cout << "callable1 should pass: 1+2=" << acc1.call(1,2) << std::endl;

 std::cin.get();
 return 0;
}

As you can see, I made a simple test case for my class. First, I try to construct and call my callables. I used a few simple functions with different signatures, they all are working fine.

After that, I perform "runtime" checks of my callables to see, if their parameter types all match the return type. The result is as expected. first and last return true, other two return false.

Then I make sure that the size of my class is acceptable. It met my expectations - 4 bytes (on my system), just as the pointer itself. Checker structs also take up only 1 byte, there really isn't much in them.

After that I do a test to see if I can get back the raw pointer, which I, of course, can. It is simply a matter of returning the private field. In this post I didn't really take any measures to process const pointers and so on, but this should be a simple addition.

Finally I wrote a test class accepts_template. It accepts a callable and can perform it (during other tasks), but it checks if it meets certain criteria, such as a non-void return type and parameters with types that match the return type. I made these checks compile-time just to see if this feature works, it does. As expected, callable1 passes and callable2 does not compile.

Conclusion 

Although I didn't realize callable completely due to my lack of time and motivation, I think that it is a good idea (at least in theory). Low memory and speed loss makes it a viable option in real applications. Furthermore, support for compile-time checks makes it a good candidate for interfaces to libraries. If you want to restrict user's access to some templated function or class, just add compile-time checks inside the function or constructor and you should be good to go. I am sure, that I will use this class in the future.

Comments