Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

[[nodiscard]] in std::function return type definition?

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!

like image 255
Alireza Hosseini Avatar asked Sep 18 '20 10:09

Alireza Hosseini


2 Answers

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}();
}
like image 195
Yamahari Avatar answered Oct 11 '22 23:10

Yamahari


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.

like image 2
bitmask Avatar answered Oct 11 '22 23:10

bitmask