Могу ли я взять адрес функции, определенной в стандартной библиотеке?

Рассмотрим следующий код:

#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";
}

Здесь два вызова std::invoke помечены для дальнейшего использования. Ожидаемый результат:

a

Ожидаемый результат гарантирован в С++ 20?

(Примечание: есть две функции, называемые tolower - одна в <cctype> а другая в <locale>. Явное приведение приведено для выбора желаемой перегрузки.)

Ответ 1

Короткий ответ

Нет.

объяснение

[namespace.std] говорит:

Обозначим через F стандартную библиотечную функцию ([global.functions]), статическую функцию-член стандартной библиотеки или создание шаблона стандартной библиотечной функции. Если F не обозначена адресуемой функцией, поведение программы C++ не определено (возможно, плохо сформировано), если она явно или неявно пытается сформировать указатель на F [Примечание: возможные способы формирования таких указателей включают применение унарного оператора & ([expr.unary.op]), addressof ([ addressof ]) или стандартное преобразование функции в указатель ([conv.func]). - примечание конца] Более того, поведение программы C++ не определено (возможно, плохо сформировано), если она пытается сформировать ссылку на F или если она пытается сформировать указатель на член, обозначающий либо стандартную библиотеку, не статическая функция-член ([member.functions]) или создание шаблона функции-члена стандартной библиотеки.

Имея это в виду, давайте проверим два вызова std::invoke.

Первый звонок

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

Здесь мы пытаемся сформировать указатель на std::boolalpha. К счастью, [fmtflags.manip] спасает день:

Каждая функция, указанная в этом подпункте, является назначенной адресуемой функцией ([namespace.std]).

И boolalpha - это функция, указанная в этом подпункте. Таким образом, эта линия правильно сформирована и эквивалентна:

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

Но почему это? Ну, это необходимо для следующего кода:

std::cout << std::boolalpha;

Второй звонок

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

К сожалению, [cctype.syn] говорит:

Содержимое и значение заголовка <cctype> совпадают с заголовком стандартной библиотеки C <ctype.h>.

Нигде tolower явно обозначена адресуемая функция.

Поэтому поведение этой программы C++ не определено (возможно, плохо сформировано), поскольку она пытается сформировать указатель на tolower, который не обозначен как адресуемая функция.

Заключение

Ожидаемый результат не гарантируется. На самом деле, код даже не гарантированно компилируется.


Это также относится к функциям-членам. [namespace.std] прямо не упоминает об этом, но из [member.functions] видно, что поведение программы C++ не определено (возможно, неправильно сформировано), если она пытается получить адрес объявленной функции-члена в стандартной библиотеке C++. По [member.functions]/2:

Для не виртуальной функции-члена, описанной в стандартной библиотеке C++, реализация может объявить другой набор сигнатур функций-членов при условии, что любой вызов функции-члена, который выберет перегрузку из набора объявлений, описанных в этом документе ведет себя так, как будто эта перегрузка была выбрана. [Примечание: Например, реализация может добавлять параметры со значениями по умолчанию или заменять функцию-член аргументами по умолчанию с двумя или более функциями-членами с эквивалентным поведением, или добавлять дополнительные подписи для имени функции-члена. - конец примечания]

И [expr.unary.op]/6:

Адрес перегруженной функции может быть взят только в контексте, который однозначно определяет, на какую версию перегруженной функции ссылаются (см. [Over.over]). [Примечание: поскольку контекст может определять, является ли операнд статической или нестатической функцией-членом, контекст также может влиять на то, имеет ли выражение тип "указатель на функцию" или "указатель на функцию-член". - конец примечания]

Следовательно, поведение программы не определено (возможно, плохо сформировано), если она явно или неявно пытается сформировать указатель на функцию-член в библиотеке C++.

(Спасибо за комментарий за указание на это!)