Home > Net >  Forward arguments to wrapped base class member
Forward arguments to wrapped base class member

Time:07-29

I want to write a callback - wrapper around std::function without operator bool, which will do nothing in operator() if wrapped std::function in empty state. Here is my idea

template <class... Arguments>
struct callback : std::function<void(Arguments...)>
{
    using base_t = std::function<void(Arguments...)>;

    using base_t::base_t;

    template <class... Args>
    void operator()(Args&&... args) const
    {
        if (!static_cast<bool>(static_cast<base_t>(*this)))
            return;
        base_t::operator()(std::forward<Args>(args)...);
    }
    
    operator bool() const = delete;
};

But using my type in the next code results in compilation error

int main()
{
    callback<int> f;
    f({});
}

The error is:

<source>:22:6: error: no match for call to '(callback<int>) (<brace-enclosed initializer list>)'
   22 |     f({});

Using the next implementation compiles, but do not suits me

template <class... Arguments>
struct callback : std::function<void(Arguments...)>
{
    using base_t = std::function<void(Arguments...)>;

    using base_t::operator();
    operator bool() const = delete;
};

Can someone please elaborate what is the difference between the two and is there a way to fix the error, but with my requirements?

CodePudding user response:

template <class... Arguments>
struct callback : std::function<void(Arguments...)>
{
    using base_t = std::function<void(Arguments...)>;

    void operator()(Arguments... args) const
    {
        if (!static_cast<bool>(*this))
            return;
        base_t::operator()(std::forward<Arguments>(args)...);
    }
    
    operator bool() const = delete;
};

this version will support the f({}) syntax, but value arguments may be moved one additional time.

If you want to avoid that, you can do this little trick:

template <class... Arguments>
struct callback : std::function<void(Arguments&&...)>
{
    using base_t = std::function<void(Arguments&&...)>;

    void operator()(Arguments... args) const
    {
        if (!static_cast<bool>(*this))
            return;
        base_t::operator()(std::forward<Arguments>(args)...);
    }
    
    operator bool() const = delete;
};

which turns the intermediate std::function into a pass-through forwarder.

Note that your static_cast<bool>(*this) also won't work.

if (!static_cast<base_t const&>(*this))

may be what you want.

Myself, I might go a step further.

template<class T>
struct to_optional {
  using type=std::optional<T>;
};
template<class T>
using to_optional_t = typename to_optional<T>::type;

template<class T>
struct to_optional<std::optional<T>>:to_optional<T>{};

template<class Sig>
struct callback;

template <class R, class... Args>
struct callback<R(Args...)> : std::function<R(Args&&...)>
{
    using base_t = std::function<R(Args&&...)>;

    to_optional_t<R> operator()(Args... args) const
    {
        if (!static_cast<bool>(*this))
            return std::nullopt;
        return base_t::operator()(std::forward<Args>(args)...);
    }
    
    operator bool() const = delete;
};
template <class... Args>
struct callback<void(Args...)> : std::function<void(Args&&...)>
{
    using base_t = std::function<R(Args&&...)>;

    bool operator()(Args... args) const
    {
        if (!static_cast<bool>(*this))
            return false;
        base_t::operator()(std::forward<Args>(args)...);
        return true;
    }
    
    operator bool() const = delete;
};

which allows return values and testing if the call occurred.

You'll also want to inherit the ctor of std::function, possibly guarding against invoking some methods with callback objects themselves. It gets tricky.


Note that this is less insane than you might think. Arguments of type Args... can be constructed from arguments of type Args&&... so long as move-construction is possible.

So callback<bob, stacy, alice> will work; a final function which takes (bob, stacy, alice) can be stored in a std::function<void(bob&&, stacy&&, alice&&)>.

CodePudding user response:

Can someone please elaborate what is the difference between the two

{} doesn't have any type. So in the first case Args cannot be deduced and hence we get the error.

In the second case, for callback<int>, using base_t::operator() will be the same as using std::function<void (int)>::operator(). And since {} is a valid argument for int, this works without any problem. This can be seen from over.ics.list#9.2:

if the initializer list has no elements, the implicit conversion sequence is the identity conversion. [ Example:

 void f(int);

 f( { } );               // OK: identity conversion

 — end example ]

  • Related