C언어

[C11] C언어도 제네릭이? _Generic 키워드 살펴보기

카루-R 2023. 12. 31. 22:46
반응형

환영합니다, 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과 함께 이런 식으로도 응용이 가능합니다. 변수가 특정 타입이 아니라면 컴파일 오류를 때려버리기. 만약 매크로 등을 이용해서 함수를 호출했는데, 함수를 호출하기 전 타입이 정확히 들어왔는지 체크하기에 좋겠죠.

 
반응형