static_assert 선언

static_assert 선언 (static_assert declaration, C++11부터)

조건이 참인지 컴파일 시점에 확인하고 싶을 때 쓰는 게 static_assert예요. 프로그램이 컴파일되는 동안 검사가 실행되어서, 조건이 거짓이면 컴파일이 실패해요. 이 페이지에서는 static_assert의 문법과, 검사가 실제로 어떻게 수행되는지 정리할게요.

출처: cppreference

본문

static_assert는 컴파일 시점 단언(assertion) 검사를 수행해요. 조건이 거짓이면 프로그램은 ill-formed가 되고, 진단 오류 메시지가 생성될 수 있어요.

문법

static_assert(bool-constexpr,unevaluated-string) (1)
static_assert(bool-constexpr) (2) (C++17부터)
static_assert(bool-constexpr,constant-expression) (3) (C++26부터)
  1. 고정된 오류 메시지를 갖는 static assertion.
  2. 오류 메시지가 없는 static assertion.
  3. 사용자가 생성한 오류 메시지를 갖는 static assertion. 이 문법은 문법 (1)이 매칭되지 않을 때만 매칭될 수 있어요.

동작 방식 (Explanation)

static_assert 선언은 네임스페이스와 블록 범위에 (블록 선언으로), 그리고 클래스 본문 안에 (멤버 선언으로) 나타날 수 있어요.

bool-constexpr이 잘 구성되어 true로 평가되거나, 템플릿 정의 맥락에서 평가되고 그 템플릿이 인스턴스화되지 않은 경우 이 선언은 효과가 없어요. 그렇지 않으면 컴파일 시점 오류가 발생하고, 사용자가 제공한 메시지가 있으면 그 메시지가 진단 메시지에 포함돼요.

사용자가 제공한 메시지의 텍스트는 다음과 같이 결정돼요:

  • 메시지가 unevaluated-string의 문법 요구를 충족하면, 그 메시지의 텍스트는 unevaluated-string의 텍스트예요.

주의할 점 (Notes)

표준은 컴파일러가 오류 메시지의 정확한 텍스트를 출력하도록 요구하지 않아요. 다만 컴파일러는 대개 가능한 한 그렇게 해요.

오류 메시지는 문자열 리터럴이어야 하므로, 동적 정보나 (문자열 리터럴 그 자체가 아닌) 상수 표현식도 담을 수 없어요. 특히 템플릿 타입 인자이름을 담을 수 없어요. (C++26 이전)

키워드

static_assert

예제

여기서는 static_assert가 실제로 어떤 시점에 무엇을 검사하는지 확인해 볼게요. 특히 템플릿 안에서의 동작이 관건이에요.

#include <format>
#include <type_traits>

static_assert(03301 == 1729); // C++17부터 메시지 문자열은 선택 사항

template<class T>
void swap(T& a, T& b) noexcept
{
    static_assert(std::is_copy_constructible_v<T>,
                  "Swap requires copying");
    static_assert(std::is_nothrow_copy_constructible_v<T> &&
                  std::is_nothrow_copy_assignable_v<T>,
                  "Swap requires nothrow copy/assign");
    auto c = b;
    b = a;
    a = c;
}

template<class T>
struct data_structure
{
    static_assert(std::is_default_constructible_v<T>,
                  "Data structure requires default-constructible elements");
};

template<class>
constexpr bool dependent_false = false; // CWG2518/P2593R1 이전의 우회책

template<class T>
struct bad_type
{
    static_assert(dependent_false<T>,
                  "error on instantiation, workaround");
    static_assert(false,
                  "error on instantiation"); // CWG2518/P2593R1 덕분에 OK
};

struct no_copy
{
    no_copy(const no_copy&) = delete;
    no_copy() = default;
};

struct no_default
{
    no_default() = delete;
};

#if __cpp_static_assert >= 202306L // 아직 실제 C++은 아님 (std::format이 constexpr이어야 동작):
static_assert(sizeof(int) == 4,
              std::format("Expected 4, got {}", sizeof(int)));
#endif

int main()
{
    int a, b;
    swap(a, b);

    no_copy nc_a, nc_b;
    swap(nc_a, nc_b); // 1

    [[maybe_unused]] data_structure<int> ds_ok;
    [[maybe_unused]] data_structure<no_default> ds_error; // 2
}

가능한 출력:

1: error: static assertion failed: Swap requires copying
2: error: static assertion failed: Data structure requires default-constructible elements
3: error: static assertion failed: Expected 4, got 2

먼저 static_assert(03301 == 1729)는 C++17부터 메시지 인자를 생략할 수 있다는 걸 보여줘요. swapno_copy와 함께 쓰이면 복사 생성이 불가능하므로 1번 static_assert가 실패해요. 그리고 data_structure<no_default>는 기본 생성이 불가능한 타입이라 2번에서 실패하고요. 이처럼 컴파일 타임에 타입 특성을 점검할 수 있게 해주는 게 static_assert예요.

더 알아보기

  • #error 지시문: 주어진 오류 메시지를 표시하고 프로그램을 ill-formed로 만든다 (전처리 지시문).
  • assert: 사용자가 지정한 조건이 참이 아니면 프로그램을 중단한다 (함수 매크로).
  • contract_assert(C++26): 실행 중에 내부 조건을 검증한다.
  • Type traits(C++11): 타입의 속성을 조회하는 컴파일 타임 템플릿 기반 인터페이스를 정의한다.