Почему назначение указателя функции работает в прямом назначении, но не в условном операторе - программирование

Почему назначение указателя функции работает в прямом назначении, но не в условном операторе

(В этом примере не использовались #include, скомпилированные в MacOS10.14, Eclipse IDE, с g++, параметры -O0 -g3 -Wall -c -fmessage-length = 0)

Предполагая это объявление переменной:

int (*fun)(int);

Это не скомпилируется с "недопустимой перегрузкой std :: toupper и std :: tolower".

fun = (1 ? std::toupper : std::tolower);   // ERROR, invalid overload

И это компилируется нормально:

if (1) {
    fun = std::toupper;   // OK
}
else {
    fun = std::tolower;   // OK
}
4b9b3361

Ответ 1

std::toupper (1 и 2) и std::tolower (1 и 2) перегружены. При определении общего типа между ними для условного оператора (до присвоения chr2fun), какую перегрузку следует использовать, невозможно определить.

Вы можете использовать static_cast чтобы указать, какой из них следует учитывать. (Предполагается, что принудительное разрешение перегрузки происходит сначала соответственно, затем проблема определения общего типа исчезает.)

static_cast также может использоваться для устранения неоднозначности перегрузок функций путем выполнения преобразования функции в указатель в конкретный тип

например

chr2fun = (str2modus == STR2UP ? static_cast<int(*)(int)>(std::toupper) 
                               : static_cast<int(*)(int)>(std::tolower));

Во втором случае chr2fun назначается напрямую; тип chr2fun является явным, и правильная перегрузка будет выбрана в разрешении перегрузки.

(акцент мой)

Во всех этих контекстах функция, выбранная из набора перегрузки, является функцией, тип которой соответствует указателю на функцию, ссылке на функцию или указателю на тип функции-члена, ожидаемый целью: объект или ссылка инициализируются, левая рука сторона присваивания, параметр функции или оператора, тип возвращаемого значения функции, целевой тип приведения или тип параметра шаблона соответственно.

Ответ 2

В первом случае компилятор блокируется, даже не добравшись до назначения. Упрощенное выражение:

(true ? std::toupper : std::tolower)

Не удастся скомпилировать, если имеется несколько перегрузок присутствующего toupper/tolower. Это связано с тем, что тип возвращаемого тернарного оператора должен быть установлен исключительно на основе типов 2-го и 3-го аргумента, не обращая внимания на контекст, в котором используется его результат.

Забавно, даже если один из этих аргументов не является перегруженной функцией, этого все же недостаточно. Причины этого менее очевидны и имеют большее отношение к правилам разрешения 1перегрузки и их применению. Приведение - это точно одна из семи возможностей его запуска, а определение целевого типа троичных операторов само по себе не является.

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

В любом случае, как указывает @Caleth, согласно 16.5.4.2.1.6, этот код имеет неопределенное поведение.


1 Ссылка C++ содержит неверный параграф стандарта C++. [over.over] на самом деле 12.4.

Ответ 3

Этот фрагмент прекрасно компилируется с gcc 9.1.

#include <cctype>

int chr2fun(bool str2modus) {
    const bool STR2UP = true;
    int (*chr2fun)(int);

    if (str2modus == STR2UP) {
        chr2fun = std::toupper;
    } else {
        chr2fun = std::tolower;
    }
    chr2fun = (str2modus == STR2UP ? std::toupper : std::tolower);
}

На какой платформе и с каким компилятором вы получаете ошибку?