6

I know this question has been asked before, but despite being a fairly experienced coder I don't understand the answers and see no way to respond to these previous questions to ask for clarification. There is no "reply" link or anything. Besides, those questions were quite old. So, I'm asking the question afresh.

I have a class, in which I am overloading the += operator. I want one overload to take a bare function pointer, and the other to take a std::function:

void operator+=(void (*handler)());
void operator+=(function<void (void *, T)> handler);

Usage:

MyClass a;
a += [](){ DoSomething(); };
a += [](void *x, T y){ DoSomething(); };

Unfortunately, the code does not compile because the compiler cannot determine that the second overload is unsuitable for the first += call.

How do I define my operator+= member functions to correct this problem? I don't want to change how the operators are used (e.g. by using an explicit cast). I want them to work as demonstrated above.

In addition, it would be handy to have the following overload as well:

void operator+=(function<void()> handler);

But again, I can't overload based on the signature of the function<> template.

See this thread for further examples: Isn't the template argument (the signature) of std::function part of its type? (I tried implementing the various solutions mentioned in that thread, and none of them would compile)

I've been programming for many years in a number of languages, but my C++ skills are a little rusty.

Community
  • 1
  • 1
Sod Almighty
  • 1,768
  • 1
  • 16
  • 29
  • the **big** question is: __what are you going to do w/ a given function pointer or `std::function` object in your overload `operator+=`?__ – zaufi Dec 11 '12 at 01:54
  • @zaufi: I fail to see how that's relevant, but alright. I'm going to add it to a std::vector of event handler functions. I'm essentially implementing .NET events in C++. Because the Powers That Be still haven't wrapped their heads around the fact that Events and Properties are - like Closures - absolutely essential functionality for a modern programming language. – Sod Almighty Dec 11 '12 at 02:11
  • so, then you have to have an `std::vector` instantiated with `std::function` with exacly one (I suggest `void()`) signature... Am I right? – zaufi Dec 11 '12 at 02:14
  • @zaufi: I'm not sure I follow. Whenever I call the += operator, I want the passed function<> (or raw function pointer) to be added to the vector of functions. The problem is getting the compiler to choose the correct overload. – Sod Almighty Dec 11 '12 at 03:42

2 Answers2

3

Following code works well with g++ 4.7.2:

#include <functional>
#include <iostream>

template <typename T>
typename std::enable_if<std::is_convertible<T, void(*)()>::value>::type
foo(T&&)
{
    std::cout << "foo(void(*)())" << std::endl;
}

void foo(std::function<void(void*,int)>)
{
    std::cout << "foo(std::function<void(void*,int)>)" << std::endl;
}

int main()
{
    foo([]{});
    foo([](void*,int){});
}

The problem is caused by the constructor of std::function, which is declared as template <class F> function(F);. That constructor accepts everything as its argument. If that argument doesn't have an appropriate operator(), an error is generated during the instantiation of the constructor.

Unfortunately the overload resolution process doesn't require any template functions to be instantiated, so the compiler believes everything can be converted to any std::function type.

hpsMouse
  • 2,004
  • 15
  • 20
  • Thanks, I've just about managed to get that into my class without problems; although things were a little hairy when I tried to cast T to a function<>. Seems I have to cast it to a void(*)() first. Cheers. – Sod Almighty Dec 11 '12 at 03:51
  • I think the main problem I've been having is that I've never heard of enable_if and don't understand it. I was vaguely aware that it might help (from looking at answers to similar questions), but didn't know how it was used. I'm still not sure how it works. – Sod Almighty Dec 11 '12 at 04:02
  • Some information about `enable_if` can be found [here](http://www.boost.org/doc/libs/1_52_0/libs/utility/enable_if.html) and [here](http://en.cppreference.com/w/cpp/types/enable_if). – hpsMouse Dec 11 '12 at 07:07
  • Thanks, I'll take a look. Oh, one more question - I still want the overload "void operator+=(function handler)" for stateful parameterless lambdas. Can that be done somehow as well? – Sod Almighty Dec 11 '12 at 15:34
  • @SodAlmighty: Might be possible. With the combination of `is_convertible` and `is_class` you can check if the argument is of some class type and cannot be converted to a plain function pointer. And a modified version of the type trait from [this question](http://stackoverflow.com/questions/9117603/how-does-this-has-member-class-template-work) can be used to detect whether the argument has a `void operator()()`. – hpsMouse Dec 12 '12 at 03:51
  • @hpsMouse, do you know if that will be fixed with the new standard? If ever? – WorldSEnder Jul 03 '15 at 14:14
  • 1
    @WorldSEnder: It's been fixed in C++14. A remark is added to restrict its participation in overload resolution and it's already supported in gcc 4.9. – hpsMouse Jul 04 '15 at 02:51
0

Ok, if you have an std::vector<std::function<void()>> you even don't need to have a bunch of overloads. The simplest way is to define a templated operator+=, possible "guarded" with std::enable_if to be turned ON only for callable object or function pointers. So passing lambdas or raw pointers will do automatic conversion for you on assign (push_back/emplace_back). Passing std::function will just assign as expected.

zaufi
  • 6,811
  • 26
  • 34
  • You're missing the point. I want to add both "[](){}" lambdas and "[](void *, T){}" lambdas to my vector. I wrap them in a common container class and add that to my vector. Trying to convince me that I don't need different lambda signatures is a futile waste of your time and mine. – Sod Almighty Dec 11 '12 at 03:59