13

I'm trying to write a class with overloaded constructors that accept std::function objects as parameters, but of course every damn thing can be implicitly cast to a std::function of any signature. Which is really helpful, naturally.

Example:

class Foo {
  Foo( std::function<void()> fn ) {
    ...code...
  }
  Foo( std::function<int()> fn ) {
    ...code...
  }
};

Foo a( []() -> void { return; } );    // Calls first constructor
Foo b( []() -> int { return 1; } );   // Calls second constructor

This won't compile, complaining that both constructors are essentially identical and ambiguous. Which is nonsense, of course. I've tried enable_if, is_same and a bunch of other things. Accepting function pointers is out of the question, because that would prevent the passing of stateful lambdas. Surely there must be a way to achieve this?

My templating skills are a little lacking, I'm afraid. Normal template classes and functions are easy enough, but playing silly buggers with the compiler is a little out of my league. Can someone help me out, please?

I know variants of this question have been asked before, but they generally focus on normal functions rather than constructors; or overloading by arguments instead of return types.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Sod Almighty
  • 1,768
  • 1
  • 16
  • 29
  • 2
    Is it the case that you are expecting the argument to the constructor to be something callable with no parameters (although with an arbitrary return type)? Or are you expecting *any* signature (in which case I'm wondering what the constructor can do at all with such a thing)? – Luc Danton May 11 '13 at 03:49
  • @Luc: I'm expecting a specific signature - or signatures. For example, I may want an overload accepting a `function`, one accepting a `function`, and one accepting a `function`. In this particular case, I only require the first two; but the principle is the same. It's very simple: **I want to overload on the type of a `function<>` parameter**. This should not be an unreasonable expectation. If I can overload on other things (ints, raw function pointers), there ought to be a way to say "I am expecting a `function<>` object with *a specific signature*. – Sod Almighty May 11 '13 at 17:37

3 Answers3

5

Here are some common scenarios, and why I don't think std::function is appropriate for them:

struct event_queue {
    using event = std::function<void()>;
    std::vector<event> events;

    void add(event e)
    { events.emplace_back(std::move(e)); }
};

In this straightforward situation functors of a particular signature are stored. In that light my recommendation seems pretty bad, doesn't it? What could go wrong? Things like queue.add([foo, &bar] { foo.(bar, baz); }) work well, and type-erasure is precisely the feature you want, since presumably functors of heterogeneous types will be stored, so its costs are not a problem. That is in fact one situation where arguably using std::function<void()> in the signature of add is acceptable. But read on!

At some point in the future you realize some events could use some information when they're called back -- so you attempt:

struct event_queue {
    using event = std::function<void()>;
    // context_type is the useful information we can optionally
    // provide to events
    using rich_event = std::function<void(context_type)>;
    std::vector<event> events;
    std::vector<rich_event> rich_events;

    void add(event e) { events.emplace_back(std::move(e)); }
    void add(rich_event e) { rich_events.emplace_back(std::move(e)); }
};

The problem with that is that something as simple as queue.add([] {}) is only guaranteed to work for C++14 -- in C++11 a compiler is allowed to reject the code. (Recent enough libstdc++ and libc++ are two implementations that already follow C++14 in that respect.) Things like event_queue::event e = [] {}; queue.add(e); still work though! So maybe it's fine to use as long as you're coding against C++14.

However, even with C++14 this feature of std::function<Sig> might not always do what you want. Consider the following, which is invalid right now and will be in C++14 as well:

void f(std::function<int()>);
void f(std::function<void()>);

// Boom
f([] { return 4; });

For good reason, too: std::function<void()> f = [] { return 4; }; is not an error and works fine. The return value is ignored and forgotten.

Sometimes std::function is used in tandem with template deduction as seen in this question and that one. This tends to add a further layer of pain and hardships.


Simply put, std::function<Sig> is not specially-handled in the Standard library. It remains a user-defined type (in the sense that it's unlike e.g. int) which follows normal overload resolution, conversion and template deduction rules. Those rules are really complicated and interact with one another -- it's not a service to the user of an interface that they have to keep these in mind to pass a callable object to it. std::function<Sig> has that tragic allure where it looks like it helps make an interface concise and more readable, but that really only holds true as long as you don't overload such an interface.

I personally have a slew of traits that can check whether a type is callable according to a signature or not. Combined with expressive EnableIf or Requires clauses I can still maintain an acceptably readable interface. In turn, combined with some ranked overloads I can presumably achieve the logic of 'call this overload if functor yields something convertible to int when called with no arguments, or fallback to this overload otherwise'. This could look like:

class Foo {
public:
    // assuming is_callable<F, int()> is a subset of
    // is_callable<F, void()>
    template<typename Functor,
             Requires<is_callable<Functor, void()>>...>
    Foo(Functor f)
        : Foo(std::move(f), select_overload {})
    {}

private:
    // assuming choice<0> is preferred over choice<1> by
    // overload resolution

    template<typename Functor,
             EnableIf<is_callable<Functor, int()>>...>
    Foo(Functor f, choice<0>);
    template<typename Functor,
             EnableIf<is_callable<Functor, void()>>...>
    Foo(Functor f, choice<1>);
};

Note that traits in the spirit of is_callable check for a given signatures -- that is, they check against some given arguments and some expected return type. They do not perform introspection, so they behave well in the face of e.g. overloaded functors.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 1
    I want to know where all these comments went. – Sod Almighty May 12 '13 at 12:23
  • You _could_ ask on meta. But this is kind of a FAQ there. Likely they were cleaned by a mod. Most often it happens only when attention has been drawn to a (prolonged) comment discussion because an individual message was flagged for moderator attention. See [Meta] for _rationale_ (if such a thing really exists - prepare to disagree, like most of us) – sehe May 13 '13 at 08:48
  • I see. So, if anyone added comments before that happened, I didn't see them. So I don't know if anyone is still trying to help or not. As things stand, I still have no solution. – Sod Almighty May 13 '13 at 13:53
  • @SodAlmighty I did offer a correction of your latest code. Mostly [you were missing some `typename`](http://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords). – Luc Danton May 13 '13 at 21:09
  • @Luc: Could you link me to the correction, please? The trigger-happy mod deleted it before I spotted it. – Sod Almighty May 13 '13 at 21:52
  • @SodAlmighty I don't have it on-hand. – Luc Danton May 14 '13 at 00:47
2

So there are many ways to approach this, which take various amounts of work. None of them are completely trivial.

First, you can unpack signatures of passed in types by examining T::operator() and/or checking if it is of type R (*)(Args...).

Then check for signature equivalence.

A second approach is to detect call compatibility. Write a traits class like this:

template<typename Signature, typename F>
struct call_compatible;

which is either std::true_type or std::false_type depending on if decltype<F&>()( declval<Args>()... ) is convertible to the Signature return value. In this case, this would solve your problem.

Now, more work needs to be done if the two signatures you are overloading on are compatible. Ie, imagine if you have a std::function<void(double)> and std::function<void(int)> -- they are cross call-compatible.

To determine which is the "best" one, you can look over here at a previous question of mine, where we can take a bunch of signatures and find which matches best. Then do a return type check. This is getting complex!

If we use the call_compatible solution, what you end up doing is this:

template<size_t>
struct unique_type { enum class type {}; };
template<bool b, size_t n=0>
using EnableIf = typename std::enable_if<b, typename unique_type<n>::type>::type;

class Foo {
  template<typename Lambda, EnableIf<
    call_compatible<void(), Lambda>::value
    , 0
  >...>
  Foo( Lambda&& f ) {
    std::function<void()> fn = f;
    // code
  }
  template<typename Lambda, EnableIf<
    call_compatible<int(), Lambda>::value
    , 1
  >...>
  Foo( Lambda&& f ) {
    std::function<int()> fn = f;
    // code
  }
};

which is the general pattern for the other solutions.

Here is a first stab at call_compatible:

template<typename Sig, typename F, typename=void>
struct call_compatible:std::false_type {};

template<typename R, typename...Args, typename F>
struct call_compatible<R(Args...), F, typename std::enable_if<
  std::is_convertible<
    decltype(
      std::declval<F&>()( std::declval<Args>()... )
    )
    , R
  >::value
>::type>:std::true_type {};

that is untested/uncompiled as yet.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I don't know how to compare the type of T::operator() with void*(int). I tried `template Foo(typename enable_if::value, T>::type fn)` and it didn't work. I will look at your second suggestion. – Sod Almighty May 11 '13 at 01:19
  • With the second suggestion, I get odd errors like "type is not a member of global namespace" and such. I don't think Visual Studio 2012 supports variadic templates. Sigh. – Sod Almighty May 11 '13 at 01:24
  • @SodAlmighty Oh, you are using VS2012? v2 won't work unless you do some serious hackery, as VS2012 support for SFINAE based off `decltype` is anemic. Try v1 -- the type of `T::operator()` is a member function pointer not a function pointer (`R (T::*)(Args...)` in general, and something like `void(T::*)()` specifically). I'd write a traits class that says "is nullary and returns `void`" and "is nullary and returns `int`" and get them working, then try to `enable_if` SFINAE with them. – Yakk - Adam Nevraumont May 11 '13 at 02:13
  • I'm afraid I'm still having trouble. I have the following code: `class Foo { template Foo(typename enable_if::value, T>::type fn) { } template Foo(typename enable_if::value, T>::type fn) { } }; function fn1 = [](int n) { return; }; Foo a( fn1 ); ` and I get errors such as: "error C2535: 'Foo::Foo(enable_if::type)' : member function already defined or declared" and "C2664: 'Foo::Foo(const Foo &)' : cannot convert parameter 1 from 'std::function<_Fty>' to 'const Foo &'" – Sod Almighty May 11 '13 at 17:41
  • Also "warning C4346: 'T::()' : dependent name is not a type" and "IntelliSense: no instance of constructor "Foo::Foo" matches the argument list argument types are: (std::function)". Where am I going wrong? – Sod Almighty May 11 '13 at 17:43
  • 3
    If you go down this route (examining `T::operator()` *gasp* sacrilege!) keep in mind that you will be saying no to any and all polymorphic function objects. – R. Martinho Fernandes May 11 '13 at 22:56
  • @R: I'm not sure what you mean by that. Surely any object defining an operator() of the correct signature would be accepted? As least, if I can get the damn thing to work, which isn't looking very likely. – Sod Almighty May 12 '13 at 00:37
  • @Sod No. As I said, polymorphic function objects that happen to support that signature won't be accepted. – R. Martinho Fernandes May 12 '13 at 00:51
  • @R: I don't understand. I'm not even sure what a "polymorphic function object" is. I know what polymorphic means, and I know what a function object is. Do you mean a function that takes a more-derived type and returns a less-derived type will fail to match? If so, it's still better than what I have now, which is a compile error. – Sod Almighty May 12 '13 at 00:59
  • 1
    Ah, you could have asked what a polymorphic function object is. Two examples: `struct one_polymorphic_callable { template void operator()(T); }; struct another_polymorphic_callable { template void operator()(int); void operator()(std::vector); };`. They can be called with different forms, and thus are polymorphic. Inspecting `T::operator()` won't work because there isn't *one* `T::operator()` to inspect. – R. Martinho Fernandes May 12 '13 at 01:02
  • @R: Ah, I see. In any event, lacking a perfect solution, an imperfect solution beats a compile error. Which, as I say, is what I have at the moment until I can somehow disambiguate these two constructors. – Sod Almighty May 12 '13 at 01:14
  • @SodAlmighty when I next get some time, I'll try to work at it -- but in your particular narrow case, all you want to do is detect the return type of your `operator()` and detect if it is `void` or `int`. This is a narrower problem that will require some fancy jiggery pokery involving pointers-to-member functions and template functions and type deduction in MSVC2012 due to their mediocre C++11 support as yet. – Yakk - Adam Nevraumont May 12 '13 at 01:28
0

So I have a completely new solution to this problem that works in MSVC 2013, and doesn't suck (like looking at a pointer to operator()).

Tag dispatch it.

A tag that can carry any type:

template<class T> struct tag{using type=T;};

A function that takes a type expression, and generates a type tag:

template<class CallExpr>
tag<typename std::result_of<CallExpr>::type>
result_of_f( tag<CallExpr>={} ) { return {}; }

and use:

class Foo {
private:
  Foo( std::function<void()> fn, tag<void> ) {
    ...code...
  }
  Foo( std::function<int()> fn, tag<int> ) {
    ...code...
  }
public:
  template<class F>
  Foo( F&& f ):Foo( std::forward<F>(f), result_of_f<F&()>() ) {}
};

and now Foo(something) forwards to either the std::function<int()> or std::function<void()> constructor depending on the context.

We can make tag<> smarter by supporting conversion if you like by adding a ctor to it. Then a function that returns double will dispatch to tag<int>:

template<class T> struct tag{
  using type=T;
  tag(tag const&)=default;
  tag()=default;
  template<class U,
    class=typename std::enable_if<std::is_convertible<U,T>::value>::type
  >
  tag( tag<U> ){}
};

note that this does not support SFINAE-like Foo-construction failure. Ie, if you pass int to it, it will get a hard failure, not a soft one.

While this does not directly work in VS2012, you could forward to an initialization function in the body of the constructor.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524