Разрешение перегрузки для внешних версий C и C++ qsort()/bsearch()

В C++ стандартная библиотека предоставляет две версии qsort():

extern "C" void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
extern "C++" void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));

bsearch() примерно так же.

Мой вопрос: как работает разрешение перегрузки при вызове qsort()? Связывается ли он автоматически с соответствующей функцией на основе типа связи ("C" или "C++") указателя функции, переданного в качестве последнего аргумента? Или вызывающему абоненту нужно явно указать какой-то дополнительный синтаксис?

(Давайте просто отбросим на секунду искушение позвонить std::sort...)


person goodbyeera    schedule 22.02.2014    source источник
comment
Я никогда не видел extern C++, extern C просит компилятор C++ использовать искажение C. Где вы видели extern C++ void qsort....   -  person mpromonet    schedule 22.02.2014
comment
@mpromonet: стандарт С++.   -  person goodbyeera    schedule 22.02.2014
comment
Я не понимаю вашего вопроса, однако, возможно, запуск nm на вашем исполняемом файле (или библиотеке) ответит на ваш вопрос?   -  person mpromonet    schedule 22.02.2014
comment
@mpromonet Вопрос мне совершенно ясен. Запуск nm никоим образом не поможет при работе с реализацией, которая не реализует этот аспект C++ так, как указано в стандарте.   -  person    schedule 22.02.2014


Ответы (1)


Параметр int (*compar)(const void*, const void*) имеет разные типы для двух разных перегрузок. Для первой перегрузки это параметр указателя функции extern "C". Для второй перегрузки это параметр указателя функции extern "C++". Любой указатель функции, который вы передаете qsort, уже будет иметь какую-то связь, и именно это используется для определения того, какую перегрузку вызывать.

Цитирую стандарт:

7.5 Спецификации связи [dcl.link]

Все типы функций, имена функций с внешней связью и имена переменных с внешней связью имеют языковую связь. [...] Языковая связь по умолчанию для всех типов функций, имен функций и имен переменных - это языковая связь C++. Два типа функций с разными языковыми связями являются разными типами, даже если в остальном они идентичны.

На самом деле, я не думаю, что стандарт на самом деле требует, чтобы две перегрузки qsort действительно имели разную связь. В отличие от C, пользовательские объявления стандартных библиотечных функций не допускаются; существенная разница между ними заключается в типе compar. Они могли быть объявлены как

extern "C" typedef int (*__compar_fnp_c)(const void *, const void *);
extern "C++" typedef int (*__compar_fnp_cxx)(const void *, const void *);
void qsort(void* base, size_t nmemb, size_t size, __compar_fnp_c compar);
void qsort(void* base, size_t nmemb, size_t size, __compar_fnp_cxx compar);

где должно быть более очевидно, что __compar_fnp_c и __compar_fnp_cxx являются разными типами. Тем не менее, правило «как если бы» не допускает такой реализации, так как это нарушит код, принимающий указатель или ссылку на qsort.

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

person Community    schedule 22.02.2014
comment
Я не уверен, является ли связь указателя функции частью его сигнатуры типа. Скажем, я не знаю, как указать связь в объявлении указателя на функцию. Подражая синтаксису typedef, который вы использовали, я получил ошибку компиляции для объявления типа extern "C" int (*p)(int); как в g++, так и в clang++. - person goodbyeera; 22.02.2014
comment
@goodbyeera Именно эта строка, которую вы там используете, принимается как g++, так и clang++ в моей системе, версии 4.8.2 и 3.4 соответственно. (Редактировать) Если вы пытаетесь сделать это для объявления локальной переменной, то вы правы, синтаксис этого не позволяет. Синтаксис не позволяет указать тип указателя функции для объявления или определения блочной области. Вместо этого вам нужно будет использовать typedef файловой области и использовать этот typedef в вашей локальной переменной. - person ; 22.02.2014
comment
@goodbyeera Я отредактировал свой комментарий, чтобы решить эту проблему, почти в то же время, когда вы опубликовали свой новый комментарий :) - person ; 22.02.2014
comment
Большое спасибо за то, что нашли соответствующую часть стандарта. Это довольно ясно. Кажется, отдельный вопрос, уважают ли компиляторы это. Я попытался объявить функции, принимающие параметр указателя функции, который отличается только своей связью, и как g++, так и clang++ сообщили о переопределении функции. (Конечно, возможно, что мы неправильно объявляем связь с магией typedef, как вы представили.) - person goodbyeera; 22.02.2014
comment
@goodbyeera Я предполагаю, что вы, вероятно, делаете это правильно. И GCC, и clang имеют открытые ошибки (GCC PR 2316, clang bug 15563) именно об этой проблеме. Для GCC исправление для проверки концепции двухлетней давности доступен, с которым вы можете поэкспериментировать, если хотите. - person ; 22.02.2014
comment
Большое спасибо за информацию. Действительно сильные навыки поиска ~ - person goodbyeera; 22.02.2014