Может ли указатель функции с аргументом const использоваться как указатель функции с аргументом nonconst?

Возможно, название само по себе не понятно... У меня есть функция f (предоставляемая некоторой библиотекой), которая принимает в качестве аргумента указатель функции подписи void g(int*), т.е.

void f(void (*g)(int*));

Однако я хотел бы использовать его, используя функцию g (которую я определил) с сигнатурой void g(const int*). Априори, я не вижу, как это может нарушить любую const-правильность, поскольку вся подпись f говорит, что g будет только когда-либо вызываться с (не const) int* (non - const), и действительно, я могу вызвать функцию void (const int*) с аргументом не const int*.

Но GCC жалуется и говорит:

expected 'void (*)(int *)', but argument is of type 'void (*)(const int *)'

Я не вижу, как эта жалоба может быть законной, так кто-нибудь знает, не ошибается ли мое понимание этого, или если есть способ обойти это?

Ответы

Ответ 1

Кажется, вы нашли что-то, что писатели-компиляторы и авторы стандартов не учитывали. Из проекта C99 проекта n1256, §6.7.5.3, пункт 15,

соответствующие параметры должны иметь совместимые типы.

Обратите внимание, что const int * не совместимо с int *. Однако int * можно преобразовать в const int *. Из пункта 6.3.2.3, пункт 2,

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

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

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

Как последнее замечание, я скомпилировал с Clang, и компилятор сообщил мне, что предупреждение было педантичным. Тем не менее, я не просил предупреждения педантично... так что, похоже, нет способа отключить его.

warning: incompatible pointer types passing 'void (int const *)', expected 'void (*)(int *)'
      [-pedantic]

Обходной путь: Используйте явное преобразование.

void g(const int *);

f((void (*)(int *)) g);

Ответ 2

Вы правы, нет причин, по которым C должен запрещать этот вызов (кроме того, что стандарт C говорит, что он должен). U(*)(T*) должен быть подтипом U(*)(const T*), потому что int* является подтипом const int* через замену.

Почему C не позволяет этого, я не знаю.

Что касается рабочих обходов, вы можете предоставить прокси-функцию:

void foo(const int* x) { ... } // <-- the function you want to pass in

void bar(int* x) { foo(x); } // proxy

f(bar); // instead of f(foo)

Тот факт, что использование безопасного, совместимого с стандартом прокси-сервера, подобного этому, вообще должен быть достаточно доказательством того, что вызов должен был быть действительно в первую очередь.