4

I have a method which takes four optional callbacks. I want to be able to use lambdas as the callbacks, but I since these callbacks are optional and need a default value, I thought I'd use boost::optional.

Here's a direct copy-paste from my code:

typedef std::function<bool(const GameObjectRef &a, const GameObjectRef &b, cpArbiter *arbiter)> EarlyCollisionCallback;
typedef std::function<void(const GameObjectRef &a, const GameObjectRef &b, cpArbiter *arbiter)> LateCollisionCallback;
typedef boost::optional<EarlyCollisionCallback> NullableEarlyCollisionCallback;
typedef boost::optional<LateCollisionCallback> NullableLateCollisionCallback;

virtual void addCollisionMonitor(cpCollisionType a, cpCollisionType b,
                                NullableEarlyCollisionCallback onCollisionBegin = boost::none,
                                NullableEarlyCollisionCallback onCollisionPreSolve = boost::none,
                                NullableLateCollisionCallback onCollisionPostSolve = boost::none,
                                NullableLateCollisionCallback onCollisionSeparate = boost::none);

The idea is that later I could pass a lambda as callback for, say, the "preSolve" pass of the physics engine and ignore the other optional parameters. This may be bad design, I will likely write this differently. But the real question here is a c++ one. When I try to call this, I get compiler errors at the call site:

addCollisionMonitor(CollisionType::ENEMY, CollisionType::PLAYER,
                    //onCollisionBegin
                    [](const GameObjectRef &enemy, const GameObjectRef &player, cpArbiter *arb)->bool{
                            CI_LOG_D("Enemy: " << enemy->getName() << " contact player: " << player->getName());
                            return true;
                        });

No viable conversion from '(lambda at /Users/..../GameLevel.cpp:249:8)' to 'NullableEarlyCollisionCallback' (aka 'optional &, const shared_ptr &, cpArbiter *)> >')

However, if I explicitly wrap the lambda in NullableEarlyCollisionCallback() it works:

addCollisionMonitor(CollisionType::ENEMY, CollisionType::PLAYER,
                        //onCollisionBegin
                        NullableEarlyCollisionCallback([](const GameObjectRef &enemy, const GameObjectRef &player, cpArbiter *arb)->bool{
                            CI_LOG_D("Enemy: " << enemy->getName() << " contact player: " << player->getName());
                            return true;
                        }));

I'm curious why in this instance the compiler can't just convert the lambda to the optional<> type. What am I missing?

This is using Xcode 9 beta3.

TomorrowPlusX
  • 1,205
  • 2
  • 14
  • 27
  • Possible duplicate of [C++ implicit conversions](https://stackoverflow.com/questions/867462/c-implicit-conversions) – Justin Jul 15 '17 at 22:17
  • 1
    Basically, you'd need one implicit conversion from the lambda to the `std::function`, and then one implicit conversion to wrap that into a `boost::optional` – Justin Jul 15 '17 at 22:17
  • 2
    Because it's going to require two User-Defined Conversion Sequences which isn't permitted in C++'s [ICS (Implicit Conversion Sequences)](http://eel.is/c++draft/over.best.ics) rules. It would first require a conversion to from your `Lambda` to -> `EarlyCollisionCallback`, and to -> `NullableEarlyCollisionCallback` – WhiZTiM Jul 15 '17 at 22:20
  • 4
    `std::function` is already nullable. You don't really even need an `optional` here. – Miles Budnek Jul 15 '17 at 22:20
  • [std::function::operator bool](http://en.cppreference.com/w/cpp/utility/functional/function/operator_bool) – Deduplicator Jul 15 '17 at 22:21
  • This is what happens when I spend a few years in Python and Swift. Thanks everybody! – TomorrowPlusX Jul 15 '17 at 22:21

1 Answers1

2

A std::function is constructible from a nullptr_t. So constructed, when converted to bool the value will be false. If it contains a function object, then true.

Documentation:

http://en.cppreference.com/w/cpp/utility/functional/function/function

http://en.cppreference.com/w/cpp/utility/functional/function/operator_bool

demonstration:

#include <functional>

struct GameObjectRef {};
struct cpArbiter {};

struct cpCollisionType {};

typedef std::function<bool(const GameObjectRef &a, const GameObjectRef &b, cpArbiter *arbiter)> EarlyCollisionCallback;
typedef std::function<void(const GameObjectRef &a, const GameObjectRef &b, cpArbiter *arbiter)> LateCollisionCallback;

struct MyThing
{
    EarlyCollisionCallback a_, b_;
    LateCollisionCallback c_, d_;

    virtual void addCollisionMonitor(cpCollisionType a, cpCollisionType b,
                                EarlyCollisionCallback onCollisionBegin = nullptr,
                                EarlyCollisionCallback onCollisionPreSolve = nullptr,
                                LateCollisionCallback onCollisionPostSolve = nullptr,
                                LateCollisionCallback onCollisionSeparate = nullptr)
    {
        a_ = std::move(onCollisionBegin);
        b_ = std::move(onCollisionPreSolve);
        c_ = std::move(onCollisionPostSolve);
        d_ = std::move(onCollisionSeparate);
    }

    void call_callbacks(const GameObjectRef &a, const GameObjectRef &b, cpArbiter *arbiter)
    {
        if (a_) a_(a, b, arbiter);
        if (b_) b_(a, b, arbiter);
        if (c_) c_(a, b, arbiter);
        if (d_) d_(a, b, arbiter);
    }
};

int main()
{
    MyThing m;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142