Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Holding or passing around non-addressable-functions since C++20 [duplicate]

C++20 added the rule of addressable function 16.5.4.2.1 [namespace.std]/6: -- emphasis is mine --

Let F denote a standard library function ([global.functions]), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function, the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to F. [ Note: Possible means of forming such pointers include application of the unary & operator ([expr.unary.op]), addressof ([specialized.addressof]), or a function-to-pointer standard conversion ([conv.func]). — end note ] Moreover, the behavior of a C++ program is unspecified (possibly ill-formed) if it attempts to form a reference to F or if it attempts to form a pointer-to-member designating either a standard library non-static member function ([member.functions]) or an instantiation of a standard library member function template.

The math functions as far as I can tell, are not marked by the spec as addressable functions.

Does it mean that the following code is illegal since C++20 (as noted by cppreference with an example of other standard library functions):

// unspecified and illegal?
auto func = static_cast<float (*)(float, float)>(std::pow);
std::cout << func(2, 4) << std::endl;

and what about the following code, is it legal?

// legal? or unspecified and illegal?
std::function<float(float, float)> f = static_cast<float(*)(float, float)>(std::pow);
std::cout << f(2, 3) << std::endl;

  • What is the reason for this new restriction in C++20?
  • Isn't such a restriction breaking legacy code?
  • What is the right way since C++20 to hold or pass around non-addressable-functions?
like image 727
Amir Kirsh Avatar asked Jun 07 '20 20:06

Amir Kirsh


1 Answers

This rule comes to us from P0551. The wording here is "unspecified (possibly ill-formed)" - not undefined behavior, not ill-formed NDR, nothing like that.

Now, the library is largely designed, specified, and implemented, around direct use of APIs. The library specifies what x.foo(y, z) means and the implementation has to follow that specification. But there are many ways that this could be implemented - maybe foo takes some extra default arguments, or can be a template, or is an overload set.

Moreover, maybe in C++N, there's just x.foo(y, z). But then in C++N+1, there's a new proposal that adds x.foo(y) too. For instance, in C++03 there was just the one vector::push_back but now there are two.

What is the reason for this new restriction in C++20?

The reason for the restriction (and it isn't really conceptually new, it's more that it's finally articulated) is to permit changing the standard library. These sorts of changes are only observable if you take the address of one of these functions - and it's basically the library saying that it doesn't care if these changes break your code because it's your fault, not the committee's/library's.

See also Standard Library Compatibility.

Isn't such a restriction breaking legacy code?

Not really. It's more heavily frowning upon code that does that and then not being concerned if any future changes might break it.

What is the right way since C++20 to hold or pass around non-addressable-functions?

Wrap them in a lambda. That lambda can even be stateless, which allows you to still convert it to a function pointer. It's still a function pointer, but it's insulated from any future standard library changes.

like image 72
Barry Avatar answered Oct 18 '22 19:10

Barry