C++0x 미리보기 5, 정적 단언문

Twitter icon류광, 2008-03-09 20:03
정적 단언문을 언어 차원에서 지원합니다.

현재의 C++에는 정적 단언문(static assertion)을 위한 언어적 지원이 없습니다. 사실 동적 단언문(C assert 매크로) 역시 언어에서 지원하는 것이 아니라 라이브러리 차원에서 지원하는 것이고요. 정적 단언 역시 현재의 표준 안에서 라이브러리 차원으로 지원하는 것이 가능하고 실제로도 많이 쓰이고 있습니다. 현재 라이브러리 차원에서 정적 단언문을 지원하는 수단으로 유명한 것은 Boost의 BOOST_STATIC_ASSERT 매크로입니다. 이 매크로는 조건이 거짓이면 컴파일이 아예 되지 않는 코드를 생성함으로써 정적 단언 기능을 제공합니다.

그런데 매크로를 이용한 방식은 매크로 자체에서 비롯되는 단점(이름공간의 오염, 조건에 쉼표가 있을 경우 추가적인 괄호쌍이 필요함 등등)과 함께 오류 메시지가 직관적이지 않다는 단점이 있습니다. 예를 들어

   BOOST_STATIC_ASSERT(std::numeric_limits<unsigned int>::digits >= 32);

라는 정적 단언이 실패했을 때(즉 해당 컴파일러 또는 플랫폼에서 unsigned int가 32비트 이상이 아닐 때), Visua C++ 2008 Express는 다음과 같은 오류 메시지를 제시합니다.

\main.cpp(30) : error C2027: use of undefined type 
'boost::STATIC_ASSERTION_FAILURE<x>'
1>        with
1>        [
1>            x=false
1>        ]

g++의 경우는 다음과 같습니다.

main.cpp:30: error: invalid application of 'sizeof' to 
incomplete type 'boost::STATIC_ASSERTION_FAILURE<false>' 

두 경우 모두, 30행에 뭔가 문제가 있다는 점은 알 수 있지만 구체적으로 무엇이 문제인지는 명확하지가 않습니다.

언어적 지원이 없을 때, 매크로에 의존하지 않고 간결한 단언문 표기법을 만드는 것은 현실적으로 불가능하다고 봐야 할 것입니다. 단언문이 필요할 때마다

typedef static_assert_test< sizeof static_assert_if<(bool)조건 > > dummy1; 

같은 코드를 집어 넣을 수 없으니까요. 그리고 제가 알기로 위의 예처럼 템플릿 특수화에 의존하지 않고 정적 단언을 구현하는 방법은 없는 것 같은데, 이러한 템플릿 특수화에 의존하는 방식에서는 이를테면 BOOST_STATIC_ASSERT(조건, "설명 문자열"); 같은 형태의 매크로를 만들어서 컴파일러 오류 메시지에 "설명 문자열"을 포함시키게 하는 것이 구조적으로 불가능합니다(어떤 특정한 컴파일러 확장 기능에 의해 가능할 수도 있겠지만 표준을 따르지 않는 방법은 논외입니다).

언어적인 지원이 필요하다는 공감대 하에서 제안자들은 다음과 같은 구문을 제안했습니다.

static_assert ( constant-expression  ,  string-literal ) ;

이것은 함수 호출이 아니라 언어 차원의 일종의 “선언문”이므로 std:: 같은 것은 붙지 않습니다. constant-expression은 판정할 조건에 해당하는 상수 표현식입니다. 이것의 값이 0이면 컴파일러는 정적 단언이 실패한 것으로 간주하고 진단 메시지를 출력해야 합니다. 그리고 그 진단 메시지에는 반드시 string-literal로 지정된 리터럴 문자열이 포함되어야 합니다.

앞의 예에 나온 단언문은 다음과 같이 표현할 수 있습니다.

static_assert(std::numeric_limits<unsigned int>::digits >= 32, 
    "unsigned int는 32비트 이상이어야 함");

이러면 컴파일러는 예를 들어

main.cpp:30: 오류: 정적 단언 위반, "unsigned int는 32비트 이상이어야 함"

같은 오류 메시지를 출력합니다. 물론 이것은 가상의 예일 뿐이고요. N1720의 끝부분에 IBM 컴파일러의 실제 출력 예가 나옵니다.

그런데 정적 단언문에 익숙하지 않는 분이라면 컴파일을 아예 실패하게 만드는 조건을 강제한다는 것이 얼마나 유용할까 의문도 들겠지만, 플랫폼이나 컴파일러에 대한 특정한 가정을 가진 프로그램들에서는 정적 단언문이 매우 유용합니다. 예를 들어 unsigned int가 32비트 이상이라는 가정을 가진 프로그램을 unsigned int가 16비트인 플랫폼 또는 컴파일러에서 컴파일하는 것은 무의미합니다. 또한 템플릿 인수의 유효성을 점검하는 데에도 유용합니다. 예를 들어 template StaticArray; 라는 클래스에서 N이 배열 원소 개수를 의미한다면 N은 반드시 0보다 커야 합니다. 이러한 조건을 강제하는 데 정적 단언문이 유용합니다.

특히 후자는 STL 같은 템플릿 라이브러리의 학습 및 사용 난이도를 낮추는 데 큰 도움이 될 것입니다. 사실 STL을 사용하는 것이 별로 어려운 것은 아니지만, 초보자들은 항상 실수를 하기 마련입니다. 그런데 STL의(그리고 일반적으로는 템플릿 라이브러리의) 한 가지 문제는 사소한 실수에 대해 컴파일러가 엄청나게 복잡해 보이는, 그리고 실제 실수와는 전혀 무관해 보이는 오류 메시지를 만들어 낼 수 있다는 점입니다. 템플릿 라이브러리 구현자들이 정적 단언문을 적절하게 사용한다면 프로그래머의 실수를 좀 더 직접적으로 지적할 수 있게 될 것입니다.

이 부분은 "concept" 개념과도 관련이 있습니다. concept는 간단히 말하면 “vector에 담을 객체는 반드시 복사 생성자를 가지고 있어야 한다” 같은 조건을 문서가 아니라 코드에 직접 박아 넣을 수 있게 하는 것인데요. 언젠가 이 C++0x 미리보기에서 소개하겠습니다.

태그: 프로그래밍 C++ C++0x

comments powered by Disqus

예전 댓글(읽기 전용)