문자열 리터럴을 템플릿 인자로 전달하는 방법

2022. 4. 2. 22:56C++

C++에서는 다음 코드와 같이 템플릿 인자로 string literal을 전달하는 것이 허용되지 않는다.

template <const char* STR>
void Print()
{
    std::cout << STR << std::endl;
}

int main()
{
    Print<"Text">(); // ERROR!
    return 0;
}

 

이유가 궁금해서 구글링을 해보았더니 Stack Overflow 질문에서 다음과 같은 인용문을 찾았다.

Because string literals are objects with internal linkage (two string literals with the same value but in different modules are different objects), you can't use them as template arguments either:

이유는 string literal이 internal linkage를 가지고 있기 때문이다.

컴파일러는 코드 내 string literal을 발견하면 해당 위치에 char 배열의 주소값으로 대체한다. 이러한 이유로, 컴파일러는 해당 소스 파일에 배열을 정의해야 하며, 따라서 internal linkage를 가질 수 밖에 없는 것이다.

 

왜 템플릿 파라미터에 linkage가 문제가 될까?

 

internal linkage를 가지면 각 번역 단위에서 각각의 entity가 정의되게 된다.

예를 들어, 위 코드의 Print 함수 템플릿을 A.cpp, B.cpp 파일에서 Print<"Text">(); 와 같이 사용한다면 각 소스 파일에
static const char[5] = { 'T', 'e', 'x', 't', 0 }; 와 같이 전역 변수가 생성된다고 볼 수 있다. 따라서 템플릿 인자에는 각 소스 파일에 정의된 배열의 주소값이 전달되므로 A.cpp, B.cpp 각 소스 파일에서 생성한 두 개의 다른 Print 템플릿 함수가 존재하게 된다. 분명 템플릿 인자로 같은 값을 주었는데 다른 템플릿 클래스 타입, 템플릿 함수가 생성될 수 있는 문제가 생기는 것이다.

 

해결 방법은 위 질문에 달린 답변들처럼 여러가지가 있지만, 대표적으로 C++20에서의 방법을 소개한다.

(여기)에서 소개된 방법이며, C++20에서 리터럴 클래스 타입이 템플릿 파라미터로 사용될 수 있다는 점을 이용한다.

template <size_t N>
struct StringLiteral
{
    char value[N];

    constexpr StringLiteral(char const(&str)[N])
    {
        std::copy_n(str, N, value);
    }
};

template <StringLiteral STR>
void Print()
{
    std::cout << STR << std::endl;
}

int main()
{
    Print<"Text">();
    return 0;
}

string literal을 포인터 또는 레퍼런스로 직접 받을 수 없으므로, string literal을 담을 수 있는 리터럴 클래스를 정의하였으며, 리터럴 클래스로 만들기 위해 constexpr 생성자를 사용했다.

Print 함수 템플릿의 템플릿 파라미터가 StringLiteral<> 형태가 아닌 StringLiteral인 이유는 C++17부터 지원되는 Class Template Argument Deduction에 의해 StringLiteralconstexpr 생성자를 통해 템플릿 인자들이 추론되기 때문이다.

 

 

 

 

[참고]

- https://stackoverflow.com/questions/5547852/string-literals-not-allowed-as-non-type-template-parameters

- Passing String Literals as Template Parameters in C++20