Hamza Hajeir
Hamza Hajeir

Reputation: 416

Can I assign a callable to a std::function with a different return type?

I want to store a function inside a std::function. I don't care about the return value when calling this stored function, so the std::function just returns void.

Is there any undefined behavior when the stored function gets called?

Take the following example as a reference, where the function is a lambda returning an int (Link to run):

#include <iostream>
#include <functional>
std::function<void(void)> f;
int main()
{
    f = []() -> int { std::cout << "Returning int\n";  return 0; };
    f(); // Is this UB in any way?
    return 0;
}

The question applies not only to lambdas, but to any callable, such as member functions, free functions, etc.

Upvotes: 18

Views: 924

Answers (2)

Caleth
Caleth

Reputation: 63142

Ignoring the return result is explicitly allowed.

You are using the template< class F > function& operator=( F&& f ); overload of operator=, which has the requirement:

This operator does not participate in overload resolution unless f is Callable for argument types Args... and return type R.

Which is

INVOKE<R>(f, std::declval<ArgTypes>()...) is well-formed in unevaluated context.

And std::function<R(Args...>::operator()(Args... args) is similarly defined as doing INVOKE<R>(f, std::forward<Args>(args)...), returning nothing in the case R is void.

The exposition-only operation INVOKE<R>(f, arg_0, arg_1, arg_2, ..., arg_N) is defined as follows:

  • If R is (possibly cv-qualified) void

    • static_cast<void>(INVOKE(f, arg_0, arg_1, arg_2, ..., arg_N)).

I.e. it explicitly discards the result by casting to void

Upvotes: 19

Neil Butcher
Neil Butcher

Reputation: 608

This is valid code, the return will simply be discarded.

However, it is difficult to tell on a swift inspection if this was intentional or an oversight.

Clarifying that this was your intention with tools such as std::ignore will help the readability of your code

auto g =  []() -> int { std::cout << "Returning int\n";  return 0; };

f = [&g]() -> void{ std::ignore = g(); };

//use g somewhere else or refactor to eliminate g

Upvotes: 3

Related Questions