[C11] C언어도 제네릭이? _Generic 키워드 살펴보기
환영합니다, Rolling Ress의 카루입니다.
C언어에 제네릭이 이미 들어갔었네요? 물론 C++의 template이나 다른 언어의 제네릭에 비해선 크게 약하지만, 그래도 가능은 하다라는 점에 의의를 두고 싶습니다. C23에서는 아예 auto 키워드가 본래의 의미를 잃고 C++의 auto와 동일하게 변경되는데, C언어도 발전하는 모습이 보기 좋습니다. 그래봐야 실무에선 아직도 C99 쓰겠지만.
_Generic(expr, list)
표현식은 간단합니다. 이때 list에는 다음과 같은 것들이 올 수 있습니다.
// type은 VLA 또는 VLA를 가리키는 포인터가 올 수 없음.
type1: expr1,
type2: expr2,
default: expr3
C언어는 오버로딩을 지원하지 않기 때문에 매크로의 도움으로 여러 함수를 편리하게 사용할 수 있습니다. 그 첫 번째가 바로 <tgmath.h> 헤더파일입니다. Type Generic MATH의 줄임말이에요.
#include <stdio.h>
#include <tgmath.h>
#include <complex.h>
int main(void)
{
printf("sqrt(2) = %f\n", sqrt(5)); // 5: int -> sqrt(5) 호출
printf("sin(0.5f) = %f\n", sin(5.f)); // 5.f: float -> sinf 호출
double complex z = sqrt(2 + 3*I); // 2 + 3*I: double complex -> csqrt 호출
printf("sqrt(2 + 3i) = %f+%fi\n", creal(z), cimag(z));
return 0;
}

여기선 헤더파일 자체에 제네릭 선언이 되어 있기 때문에, 마치 C++처럼 편리하게 사용이 가능합니다.
자, 이제 진짜로 _Generic을 써보죠.
#include <stdio.h>
#define type_name(expr) _Generic((expr), \
char: "char", \
short: "short", \
int: "int", \
long: "long", \
long long: "long long", \
double: "double", \
char*: "string", \
default: "unknown")
int main(void) {
printf("Type name of \"karu\": %s\n", type_name("karu"));
printf("Type name of 3.0: %s\n", type_name(3.0));
printf("Type name of 3LL: %s\n", type_name(3LL));
// 아래와 같이 적절한 case와 default가 없는 상황에서
// 처리되지 않는 타입을 넣으면 컴파일 오류가 발생합니다.
// _Generic(1.f, int: "int", char: "char"));
return 0;
}

아까 VLA를 가리키는 포인터는 안 된다고 했는데, 그냥 포인터는 사용이 가능합니다. 예를 들어 char*를 받으면 문자열로 취급하고 처리하는 코드를 만들 수도 있어요.

참고로 마지막 줄의 주석을 해제하면 이런 오류가 발생합니다. 친절하죠.
#include <stdio.h>
enum TYPE { INT, DOUBLE, CHAR, OTHER };
#define type_name(expr) _Generic((expr), \
char: CHAR, int: INT, double: DOUBLE, default: OTHER)
int main(void) {
int c = 10;
// char c = 10;
_Static_assert(type_name(c) == INT, "type mismatch");
return 0;
}

참고로 _Static_assert도 C11에서 추가된 문법입니다. <assert.h>를 추가하면 그냥 static_assert로 사용할 수 있어요. 여튼, _Generic과 함께 이런 식으로도 응용이 가능합니다. 변수가 특정 타입이 아니라면 컴파일 오류를 때려버리기. 만약 매크로 등을 이용해서 함수를 호출했는데, 함수를 호출하기 전 타입이 정확히 들어왔는지 체크하기에 좋겠죠.