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 move
d 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 ]