Да!
За время, прошедшее после того, как этот вопрос был задан, стандарт C (без расширений) фактически получил поддержку перегрузки функций (не операторов), благодаря добавлению ключевого слова _Generic
в C11. (поддерживается в GCC начиная с версии 4.9)
(Перегрузка на самом деле не является «встроенной» в способе, показанном в вопросе, но реализовать что-то, что так работает, очень просто.)
_Generic
- оператор времени компиляции в том же семействе, что и sizeof
и _Alignof
. Это описано в стандартном разделе 6.5.1.1. Он принимает два основных параметра: выражение (которое не будет оцениваться во время выполнения) и список ассоциаций типа / выражения, который немного похож на блок switch
. _Generic
получает общий тип выражения и затем «переключает» его, чтобы выбрать выражение конечного результата в списке для его типа:
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
Приведенное выше выражение имеет значение 2
- тип управляющего выражения int
, поэтому в качестве значения выбирается выражение, связанное с int
. Ничего из этого не остается во время выполнения. (Предложение default
является необязательным: если вы не включите его, а тип не будет совпадать, это приведет к ошибке компиляции.)
Способ, которым это полезно для перегрузки функций, заключается в том, что он может быть вставлен препроцессором C и выбирать выражение результата на основе типа аргументов, передаваемых управляющему макросу. Итак (пример из стандарта C):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Этот макрос реализует перегруженную операцию cbrt
, отправляя тип аргумента макросу, выбирая соответствующую функцию реализации, а затем передавая исходный аргумент макроса этой функции.
Итак, чтобы реализовать ваш оригинальный пример, мы могли бы сделать это:
foo_int (int a)
foo_char (char b)
foo_float_int (float c , int d)
#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__,)), \
int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A
В этом случае мы могли бы использовать default:
ассоциацию для третьего случая, но это не демонстрирует, как распространить принцип на несколько аргументов. Конечным результатом является то, что вы можете использовать foo(...)
в своем коде, не беспокоясь (много [1]) о типе его аргументов.
Для более сложных ситуаций, например Функции, перегружающие большее количество аргументов или изменяющиеся числа, вы можете использовать служебные макросы для автоматической генерации статических структур диспетчеризации:
void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }
#define print(...) OVERLOAD(print, (__VA_ARGS__), \
(print_ii, (int, int)), \
(print_di, (double, int)), \
(print_iii, (int, int, int)) \
)
#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"
int main(void) {
print(44, 47); // prints "int, int"
print(4.4, 47); // prints "double, int"
print(1, 2, 3); // prints "int, int, int"
print(""); // prints "unknown arguments"
}
( реализация здесь ) Поэтому, приложив некоторые усилия, вы можете уменьшить количество шаблонов, чтобы они выглядели почти как язык с встроенной поддержкой перегрузки.
Кроме того, уже было возможно перегрузить число аргументов (не тип) в C99.
[1] обратите внимание, что способ, которым C оценивает типы, может сбить вас с толку. Он выберет foo_int
, если вы попытаетесь передать ему символьный литерал, например, , и вам нужно немного помешать , если вы хотите, чтобы ваши перегрузки поддерживали строковые литералы. Тем не менее, в целом довольно круто.