I'm wondering if is there a way to have something like this:
using CallbackType = std::function<[[nodiscard]]bool(void)>;
(I know above code won't be compiled and complains that nodiscard
can't be applied to types!)
My goal is to enforce the caller of the callback to check the return value of it!
you can do something like this
#include <iostream>
#include <utility>
template <typename R, typename... Args>
struct Function {
using Fn = R (*)(Args...);
Fn fn;
explicit Function(Fn fn) : fn{fn} {}
[[nodiscard]] R operator()(Args... args) {
return (*fn)(std::forward<Args>(args)...);
}
};
template <typename R, typename... Args>
Function(R (*)(Args...)) -> Function<R, Args...>;
bool bar(const int& x) { return x % 2 == 0; }
int main() {
Function{&bar}(10);
}
Warning:
Compiler stderr
<source>: In function 'int main()':
<source>:24:17: warning: ignoring return value of 'R Function<R, Args>::operator()(Args ...) [with R = bool; Args = {const int&}]', declared with attribute 'nodiscard' [-Wunused-result]
24 | Function{&bar}(10);
| ~~~~~~~~~~~~~~^~~~
<source>:12:19: note: declared here
12 | [[nodiscard]] R operator()(Args... args) {
| ^~~~~~~~
EDIT: Extend to member function (+const) + lambda ( with deduction guide )
template <typename T, typename = std::void_t<>>
struct Function;
template <typename R, typename... Args>
struct Function<R (*)(Args...)> {
using Fn = R (*)(Args...);
Fn fn;
explicit Function(Fn fn) : fn{fn} {}
[[nodiscard]] R operator()(Args... args) {
return fn(std::forward<Args>(args)...);
}
};
template <typename T, typename R, typename... Args>
struct Function<R (T::*)(Args...)> {
using MFn = R (T::*)(Args...);
T* t;
MFn mfn;
Function(T* t, MFn mfn) : t{t}, mfn{mfn} {}
[[nodiscard]] R operator()(Args... args) {
return (t->*mfn)(std::forward<Args>(args)...);
}
};
template <typename T, typename R, typename... Args>
struct Function<R (T::*)(Args...) const> {
using MFn = R (T::*)(Args...) const;
const T* t;
MFn mfn;
Function(const T* t, MFn mfn) : t{t}, mfn{mfn} {}
[[nodiscard]] R operator()(Args... args) {
return (t->*mfn)(std::forward<Args>(args)...);
}
};
template <typename T>
struct Function<T, std::void_t<decltype(&T::operator())>> final
: Function<decltype(&T::operator())> {
explicit Function(const T& t)
: Function<decltype(&T::operator())>(&t, &T::operator()) {}
};
template<typename T>
Function(T) -> Function<T>;
template<typename T, typename R, typename ...Args>
Function(T*, R(T::*)(Args...)) -> Function<R(T::*)(Args...)>;
template<typename T, typename R, typename ...Args>
Function(const T*, R(T::*)(Args...) const) -> Function<R(T::*)(Args...) const>;
void foo() {}
struct Foo
{
void foo() const {}
};
int main() {
Function{&foo}();
Function{[]{}}();
Foo foo{};
Function{&foo, &Foo::foo}();
}
You can wrap std::function
or really a lambda directly to amend an attribute such as [[nodiscard]]
.
Your comment about public inheritance being problematic is absolutely correct. However, this does not apply to private inheritance as you can control for these problems.
Edit: But if you absolutely don't like it, you can rewrite the code below to use a private member instead of private inheritance at the cost of making the code a bit more verbose but the benefit of supporting raw function pointers.
template <typename Function>
struct NoDiscard : private Function {
NoDiscard() = default;
NoDiscard(NoDiscard const&) = default;
NoDiscard(NoDiscard&&) = default;
NoDiscard& operator=(NoDiscard const&) = default;
NoDiscard& operator=(NoDiscard&&) = default;
NoDiscard(Function&& fn) : Function(std::move(fn)) {}
NoDiscard(Function const& fn) : Function(fn) {}
template <typename... Ts>
[[nodiscard]] auto operator()(Ts&&... ts) {
return Function::operator()(std::forward<Ts>(ts)...);
}
};
// convenience function to make the call look intuitive
template <typename Function>
auto add_nodiscard(Function&& f) {
return NoDiscard<Function>(std::forward<Function>(f));
}
This will allow you to simply add_nodiscard
to any functor or callable type. Example:
void test_good(auto fn) {
(void)fn();
}
void test_bad(auto fn) {
fn();
}
int main() {
test_good( ( ([](){return true;})));
test_bad ( ( ([](){return true;})));
test_good(add_nodiscard( ([](){return true;})));
test_bad (add_nodiscard( ([](){return true;}))); // warns about discarded result
test_good( (std::function<bool(void)>([](){return true;})));
test_bad ( (std::function<bool(void)>([](){return true;})));
test_good(add_nodiscard(std::function<bool(void)>([](){return true;})));
test_bad (add_nodiscard(std::function<bool(void)>([](){return true;}))); // warns about discarded result
}
live demo
This has the caveat that you cannot use this with a type that must receive a std::function
object.
I believe such a goal to be impossible because you cannot change the type of a library function or add attributes directly to it. And any wrapper you add before the std::function
call will have no effect regarding [[nodiscard]]
simply because std::function
will dutifully return the returned value from its callee.
Disclaimer: I am saying that this absolutely impossible with the sole purpose of being proven wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With