Может ли указатель функции с аргументом 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)
Тот факт, что использование безопасного, совместимого с стандартом прокси-сервера, подобного этому, вообще должен быть достаточно доказательством того, что вызов должен был быть действительно в первую очередь.