Showing posts with label decltype. Show all posts
Showing posts with label decltype. Show all posts

Tuesday, April 23, 2013

Making functions: How to provide a simple interface when working with std::function

While working on the lundi project, a minor, but not negligible, issue arose: How can we spare the user from producing manually all those std::functions from their callable objects?

The hypothetical expose function template takes std::function<R(P...)> and exposes them to the lua VM. A typical use case would be:

std::function<void(int, int)> lamarck = [](int a, int b){ 
  std::cout << a << b; 
};
expose(lamarck);

That's nice and all, but why do you need to write the signature of the function you just typed? That's redundant. The thing is, lambdas are actually function objects. The preceding lambda declaration is roughly equivalent to

struct blanche {
  void operator()(int a, int b) const { 
    std::cout << a << b; 
  }
};
std::function<void(int, int)> lamarck = blanche();

Note the braces; we're not assigning a type here.

Now, we could put pretty much anything in function objects; the only thing that makes them function objects are the presence of operator(). That would be insane, of course, to provide an implicit conversion to std::function.

Nobody is keeping you from doing it, though:

template<typename R, typename... P>
std::function<R(P...)> make_function(R(func const &)(P...)) {
  return std::function<R(P...)>(func);
}

Well, that's the easy part, but what about function objects? You can't just match a function object's call signature because it's not easily accessible. You can, though, look at his operator() to work out the parameter and return types. Note that it's not possible if operator() is a template or if it's overloaded.

This is where decltype is useful:

typedef decltype(&T::operator()) ptmf_type;
ptmf_type should look (in the case of void(int, int)) like void (mytype::*)(int, int) const, which can be matched in a template to extract all the needed types. We might as well work out directly the function type:
template<typename T>
struct ptmf_traits {
  typedef void type;
};

template<typename O, typename R, typename... P>
struct ptmf_traits<R(O::*)(P...) const> {
  typedef std::function<R(P...)> type;
};

Now, you can just use ptmf_traits<decltype(&LambdaType::operator())>::type to get a function type:

template<typename Func>
ptmf_traits<decltype(&Func::operator())>::type 
make_function_from_lambda(Func const &func) {
  return func;
}

With this function, the type of the std::function produced is fully deduced from the argument type:

auto my_func = make_function_from_lambda([](int a, int b){
  std::cout << a << b;
});
// my_func's type is std::function<void(int, int)>

Of course, you need a bit of boilerplate to accept free functions and maybe some tweaking to make it do what you want, but the core idea is there. You can find a more complete version here.

You may want to follow me on twitter.

Wednesday, August 17, 2011

SFINAE and type trait-based overloading, take two

Today I thought back about my previous post on SFINAE and found that this has_const_iterator dichotomy was actually quite ugly and ineffective.

First, the print_r function has two overloads based on if the argument has a const_iterator or not. In a real-world implementation, we would also check for operator[], iterator, and also the C++11-like begin() and end(), because in fact you could name you iterators whatever you like to. One should also take into account that other special types may need special treatement, like smart pointers. Are we going to add an argument to print_r() for each type we want to support ?

Secondly, the fact that there is no default specialization of the template make it looks poorly TMP-ish. Also, that char(*)[!(has_const_iterator::value)] = 0 is really ugly.

But the real problem is not here. It is that the struct has_const_iterator is actually of no use at all. One could just use the type traits directly as function arguments with default values :
template <typename T> void print_r(const T& v, 
    typename T::const_iterator* x = 0)
and
template <typename T> void print_r(const T& t, ...)
We can make a begin()/end() version by using some C++11 features :
template <typename T> void print_r(const T& v,
    decltype(v.begin())* x = 0, decltype(v.end())* y = 0) {
  // ...blah...
  for(auto iter = v.begin(); iter != v.end(); iter++) {
    // etc.
Wow, this is getting nicer !
Now the problem is that using decltypes and default arguments like that seem not to please the compilers too much. If you add other overloads, GCC will complain based on how you place the different templates, and MSVC won't know how to choose between the different 1 overloads(yes, really). We could fix that by using trait types and a set of capability-detecting functions. But I'll do that later.

You may want to follow me on twitter.