7

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?
Amir Kirsh
  • 12,564
  • 41
  • 74
  • One way to pass them around is to use a lambda wrapper. `std::function func = [](float a, float b) { return std::pow(a, b); };` I'm guessing that the rule is so some functions can be implemented as intrinsics (but that's just a guess; I'm not a language lawyer anymore... I hung up my spurs when C++11 came out). – Eljay Jun 07 '20 at 21:01
  • 3
    One thing is for sure, (2) has the exact same status as (1). It too forms a pointer to `pow`. – HolyBlackCat Jun 07 '20 at 21:09
  • @cigien this question points out the new restriction but it does not deal with the questions that I raise. So it's related, but does not answer my questions. – Amir Kirsh Jun 07 '20 at 21:20
  • 1
    Ok, then edit the question to just state the answers to all your questions as facts, except for the 3rd last, and last question, since that appears to be the only new questions. – cigien Jun 07 '20 at 21:23

1 Answers1

10

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.

Barry
  • 286,269
  • 29
  • 621
  • 977