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

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

#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

Гарантируется ли ожидаемый результат в C ++ 20?

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

1 Ответ

30 голосов
/ 15 апреля 2019

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

номер

Объяснение

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

Пусть F обозначает стандартную библиотечную функцию ( [global.functions] ), статическую функцию-член стандартной библиотеки или создание шаблона стандартной библиотечной функции. Если F не обозначено как адресуемая функция , поведение программы на C ++ не определено (возможно, плохо сформировано), если оно явно или неявно пытается сформировать указатель на F. [ Примечание: Возможные способы формирования таких указателей включают применение унарного оператора & ( [expr.unary.op] ), addressof ( [special. addressof] ) или стандартное преобразование функции в указатель ( [conv.func] ). & Mdash; Конечная нота ] Более того, поведение программы на 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 ++, реализация может объявить другой набор сигнатур функций-членов при условии, что любой вызов функции-члена, который выберет перегрузку из набора объявлений, описанного в этом Документ ведет себя так, как будто эта перегрузка была выбрана. [ Примечание: Например, реализация может добавить параметры со значениями по умолчанию или заменить функцию-член аргументами по умолчанию двумя или более функциями-членами с эквивалентным поведением, или добавить дополнительные подписи для имени функции-члена. & Mdash; Конечная нота ]

И [expr.unary.op] / 6 :

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

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

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

...