46

Consider the following code:

#include <cctype>
#include <functional>
#include <iostream>

int main()
{
    std::invoke(std::boolalpha, std::cout); // #1

    using ctype_func = int(*)(int);
    char c = std::invoke(static_cast<ctype_func>(std::tolower), 'A'); // #2
    std::cout << c << "\n";
}

Here, the two calls to std::invoke are labeled for future reference. The expected output is:

a

Is the expected output guaranteed in C++20?

(Note: there are two functions called tolower — one in <cctype> and the other in <locale>. The explicit cast is introduced to select the desired overload.)

L. F.
  • 19,445
  • 8
  • 48
  • 82

1 Answers1

55

Short answer

No.

Explanation

[namespace.std] says:

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.

With this in mind, let's check the two calls to std::invoke.

The first call

std::invoke(std::boolalpha, std::cout);

Here, we are attempting to form a pointer to std::boolalpha. Fortunately, [fmtflags.manip] saves the day:

Each function specified in this subclause is a designated addressable function ([namespace.std]).

And boolalpha is a function specified in this subclause. Thus, this line is well-formed, and is equivalent to:

std::cout.setf(std::ios_base::boolalpha);

But why is that? Well, it is necessary for the following code:

std::cout << std::boolalpha;

The second call

std::cout << std::invoke(static_cast<ctype_func>(std::tolower), 'A') << "\n";

Unfortunately, [cctype.syn] says:

The contents and meaning of the header <cctype> are the same as the C standard library header <ctype.h>.

Nowhere is tolower explicitly designated an addressable function.

Therefore, the behavior of this C++ program is unspecified (possibly ill-formed), because it attempts to form a pointer to tolower, which is not designated an addressable function.

Conclusion

The expected output is not guaranteed. In fact, the code is not even guaranteed to compile.


This also applies to member functions. [namespace.std] doesn’t explicitly mention this, but it can be seen from [member.functions] that the behavior of a C++ program is unspecified (possibly ill-formed) if it attempts to take the address of a member function declared in the C++ standard library. Per [member.functions]/2:

For a non-virtual member function described in the C++ standard library, an implementation may declare a different set of member function signatures, provided that any call to the member function that would select an overload from the set of declarations described in this document behaves as if that overload were selected. [ Note: For instance, an implementation may add parameters with default values, or replace a member function with default arguments with two or more member functions with equivalent behavior, or add additional signatures for a member function name. — end note ]

And [expr.unary.op]/6:

The address of an overloaded function can be taken only in a context that uniquely determines which version of the overloaded function is referred to (see [over.over]). [ Note: Since the context might determine whether the operand is a static or non-static member function, the context can also affect whether the expression has type “pointer to function” or “pointer to member function”. — end note ]

Therefore, the behavior of a program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to a member function in the C++ library.

(Thanks for the comment for pointing this out!)

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 4
    Related, [interesting read](https://akrzemi1.wordpress.com/2018/07/07/functions-in-std/) (though this article doesn't touch on the concept of an addressable function). – lubgr Apr 15 '19 at 10:32
  • 1
    It is interesting to observe that the C standard takes exactly the opposite perspective: [N1570 7.1.4](http://port70.net/~nsz/c/c11/n1570.html#7.1.4) "...unless explicitly stated otherwise in the detailed descriptions...it is permitted to take the address of a library function." (`setjmp` is an example of a C library function whose address may _not_ be taken, see [7.13p3](http://port70.net/~nsz/c/c11/n1570.html#7.13p3).) – zwol Apr 15 '19 at 17:24
  • Good to know! This shows that C++ is quite defensive in library usage ;-) – L. F. Apr 16 '19 at 09:37
  • 1
    @L.F. Does this also apply to member functions of containers? For example would `&std::vector::operator[];` be wrong? I believe this is not covered in the above citation from the standard – P.W May 10 '19 at 05:37
  • Yes, it applies to member functions. `&std::vector::operator[]` is wrong. I will update my answer to reflect this when I have time. – L. F. May 10 '19 at 07:54
  • 3
    I'd like to see what the definition of an `isn’t` is – Caleth May 10 '19 at 10:50
  • The referenced footnote [cctype#1](http://eel.is/c++draft/cctype.syn#1) also says >See also: ISO C 7.4. And [Programming languages — C, 7.4 Character handling ](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) >The header declares several *functions* useful for classifying and mapping characters. – Olaf Dietsche Mar 01 '20 at 10:53
  • @OlafDietsche Yes. The C++ standard library provides these functions, and disallows taking their addresses. – L. F. Mar 01 '20 at 12:27
  • That is very interesting. However, I think you need not care if you target a specific compiler, like GNU C++ that does not make a fuss about addressable/non-addressable functions. Just be prepared to deal with the problem if some other compiler complains. In this case, you need to use a shim in another namespace, just as `less ::operator ()` is a shim over `operator<`, which is obviously not addressable. – Christopher Yeleighton Sep 13 '20 at 16:01
  • Does it implies that using `std::transform` with `tolower` is now unspecified behavior? – ph3rin Oct 04 '20 at 19:13
  • 3
    @RinKaenbyou Yes. Technically we'll need to wrap `tolower` in a lambda. – L. F. Oct 05 '20 at 01:12
  • Wrapping the `cctype` functions would be needed even if they were addressable since you need to cast the `char` to `unsigned char` to make sure you don't pass a negative value to the `cctype` function. – Ted Lyngmo Aug 24 '23 at 13:44