C++26 승인 투표 부결, contracts는 왜 논쟁이 됐나

contracts는 함수 조건 위반을 잡아내고도 설정에 따라 실행을 그대로 이어 간다. 부결된 C++26에서 이 기능은 빠지게 될까, 지금 GCC 16으로 시험해 봐도 될까?

C++26 승인 투표 부결, contracts는 왜 논쟁이 됐나
세 줄 요약
  • C++26 초안은 국가기관 투표의 두 승인 요건을 모두 충족하지 못했으며, contracts 삭제는 아직 결정되지 않았다.
  • contracts는 조건 위반을 찾는 기능이지만, 검사를 생략하거나 위반 뒤에도 실행을 계속하는 모드가 있어 프로그램 전체의 안전성을 보장하지 않는다.
  • GCC 16은 contracts 실험을 지원하고 Clang 정식 릴리스는 아직 지원하지 않아, 내부 시험과 공개 라이브러리 도입의 조건이 다르다.

찬성 12표와 반대 9표의 분모가 다른 이유

C++26 국제표준 초안(DIS)의 승인 투표가 부결됐다. 2026년 10월 26일 현재 contracts를 삭제할지, 어떤 수정안을 다시 투표에 부칠지는 정해지지 않았다. 이미 초안 기능을 시험하던 개발자에게는 표준 확정이 늦춰진 사건이며, 사용 중인 컴파일러에서 해당 기능이 즉시 사라졌다는 뜻은 아니다.

로버트 시코드가 공개한 투표 결과는 찬성과 반대의 비율을 서로 다른 집단으로 계산한다. 참여 회원인 P회원의 찬성 비율은 20표 중 12표로 60%였다. 전체 투표 기관을 대상으로 계산한 반대 비율은 22표 중 9표로 약 41%였다. 첫 번째 집단에서 찬성 12표를 빼 얻은 반대 8표와 두 번째 집단의 반대 9표는 모순이 아니다.

집계 대상승인 요건공개된 결과
P회원의 찬반 투표다.3분의 2 이상이 찬성해야 한다.12/20, 60%로 미달했다.
전체 투표 기관의 찬반 투표다.반대가 4분의 1 이하여야 한다.9/22, 약 41%로 초과했다.

ISO/IEC 지침 §2.6.3은 두 요건을 모두 요구하며 기권을 비율 계산에서 제외한다. 이번 표는 국가기관의 투표 결과다. C++ 표준화 작업반 WG21 내부에서 이전에 contracts 채택에 찬성했던 사람 수와 직접 비교해 지지율이 얼마나 떨어졌다고 계산할 수는 없다. 투표한 주체와 묻는 안건이 다르기 때문이다.

같은 사전조건도 위반 뒤의 실행이 달라진다

contracts는 함수가 호출될 때 충족해야 할 사전조건, 반환할 때 충족해야 할 사후조건 등을 코드에 적는 기능이다. 아래는 동작을 설명하기 위한 작은 함수다. pre(count > 0)는 나누는 사람 수가 양수라는 전제를 표현한다. 함수 안에서 조건을 적는 contract_assert와 반환값에 조건을 거는 post도 같은 기능군에 속한다.

int per_person(int total, int count)
    pre(count > 0)
{
    return total / count;
}

이 함수에 count=0이 들어오면 사전조건은 거짓이다. 그러나 조건을 적었다는 사실만으로 함수 본문의 나눗셈을 반드시 막는 것은 아니다. GCC의 contracts 옵션 문서는 검사를 건너뛰는 경우, 위반을 알리고 계속하는 경우, 실행을 중단하는 경우를 따로 설명한다. 어떤 방식으로 빌드했는지가 실제 결과를 바꾼다.

평가 방식위반 시 동작위 함수에 미치는 영향
ignore다.조건 검사를 실행하지 않는다.0으로 나누는 본문에 도달할 수 있다.
observe다.위반 핸들러를 호출하고, 핸들러가 정상 반환하면 계속 실행한다.위반 사실을 기록해도 잘못된 나눗셈을 막지는 못한다.
enforce다.핸들러가 정상 반환하면 프로그램을 종료한다.정상적인 제어 흐름으로 나눗셈에 진입하지 않는다.
quick-enforce다.핸들러를 부르지 않고 프로그램을 종료한다.위반 지점에서 실행을 멈춘다.

정수의 0 나누기는 C++에서 정의되지 않은 동작이다. observe로 로그를 남긴 뒤 함수 본문을 실행하면 그 나눗셈에도 도달한다. enforce에서는 핸들러가 정상 반환할 때 프로그램을 종료한다. 예외를 던질 수 있도록 정의한 핸들러가 예외를 던지면 본문에 들어가는 대신 예외 처리 경로로 이동한다. 다만 그 예외가 noexcept 경계를 넘으면 종료된다. Contracts 설계 근거 §3.6.6은 이 동작을 설명한다.

웹 요청이나 파일에서 읽은 사람 수가 0일 수 있는 프로그램이라면, 호출 전에 입력을 검증하고 오류를 반환하는 경로가 여전히 필요하다. contracts는 호출자가 지켜야 할 전제를 표현한다. 잘못된 요청을 사용자에게 어떻게 알리고 정상 작업을 계속할지까지 설계해 주지는 않는다. 이 차이를 빼면 개발 중 버그를 찾는 기능을 외부 입력 처리 전체의 대체품으로 받아들이기 쉽다.

삭제 주장과 유지 주장이 보는 안전성

비야네 스트롭스트룹 등이 쓴 P4334R0는 contracts라는 발상 자체에는 찬성하면서도 현재 설계를 비판한다. 프로그래머가 검사할 조건을 일일이 적어야 하고, 검사를 끌 수도 있으며, 일부 함수에 조건을 붙였다고 전체 프로그램의 안전성이 입증되지는 않는다는 주장이다. 저자들이 요구하는 보장의 범위는 개별 호출에서 잘못된 값을 발견하는 것보다 넓다.

앞의 함수에 적용하면 이 주장이 구체적으로 드러난다. count > 0을 정확히 검사해도 다른 함수의 잘못된 포인터 접근을 막지는 못한다. 조건을 빠뜨린 호출에는 검사 자체가 없다. 조건식에 이미 잘못된 메모리 접근이 들어 있다면 그 식을 평가하는 일도 안전하지 않을 수 있다. 반대 측은 이런 검사를 널리 붙이는 비용과 설정의 복잡성에 비해 얻는 보장이 제한적이라고 본다.

티무르 둠러 등 16명이 작성한 P4433R0는 다른 목표를 제시한다. 런타임 계약 검사는 정적 분석이나 타입을 통한 보장을 보완하며, 모든 코드에 강제하기 어려운 검사를 선택적으로 도입하는 것에도 가치가 있다는 입장이다. 기존 코드에 사전조건을 추가해 개발·시험 중 위반을 찾는 것만으로도 실제 버그를 발견할 수 있다는 논리다.

이 설명에서도 contracts가 프로그램 전체를 안전하게 만든다는 결론은 나오지 않는다. 유지 측은 제한된 보장을 주는 도구도 유용하다고 보고, 반대 측은 그 제한과 복잡성을 현 설계로 받아들일지 묻는다. 같은 예제에서 observe가 실패를 관찰하는 데 쓸모 있다는 평가와, 실패 이후의 연산을 막지 못한다는 비판은 동시에 성립한다.

구현 경험을 두고도 양측은 서로 다른 평가를 내린다. P4334R0는 라이브러리 실험이 contract_assert에 한정됐고 새 pre·post를 추가하거나 대규모 제품에 배포한 경험이 부족하다고 비판한다. 반면 P4433R0는 여러 큰 코드베이스에 적용해 기존 버그를 찾았다고 반박한다. 새 선언 문법이 아직 국제표준으로 발행되기 전에 확보할 수 있는 적용 경험을 모았으며, 다른 언어 기능의 표준화 당시와 비교해야 한다는 주장이다.

표준 라이브러리는 왜 observe만으로 부족한가

이 논쟁은 표준 라이브러리의 오류 검사와도 연결된다. 라이브러리가 접근 범위 같은 사전조건을 검사하는 강화 구현을 제공할 때, 실패를 발견한 뒤 계속 진행하면 곧바로 정의되지 않은 동작에 들어갈 수 있다. 예를 들어 존재하지 않는 요소에 접근하는 연산 앞에서 범위 위반을 기록했더라도, 연산을 그대로 수행하면 검사가 접근을 차단하지 못한다.

P3878R1은 이 문제 때문에 강화된 사전조건에 종료를 요구하는 평가 방식을 쓰도록 제안했다. 이후 P4433R0는 해당 변경이 2025년 11월 채택돼 enforce 또는 quick-enforce를 쓰고 observe를 제외하게 됐다고 설명한다. 선택적 contracts 기능 전체와, 표준 라이브러리가 강화된 검사에 적용하는 요건은 이렇게 다를 수 있다.

유지 측이 삭제를 단순한 문법 한 덩어리의 제거로 보지 않는 이유도 여기에 있다. 이미 contracts에 기대어 작성한 라이브러리 명세를 다른 방식으로 다시 표현해야 한다는 것이다. P4433R0 저자들은 이런 명세 재작성과 검토가 필요하므로 삭제 뒤 재투표를 단순한 일정 연장으로 볼 수 없다고 주장한다.

GCC 실험과 Clang 배포는 출발점이 다르다

GCC의 C++26 지원표는 P2900R14 contracts를 GCC 16 지원 항목으로 표시한다. 반면 Clang의 정식 지원표에서 같은 제안은 아직 ‘No’다. P4433R0에 등장하는 Clang 구현은 공개된 실험 구현을 가리키며, 정식 릴리스에서 바로 쓸 수 있다는 뜻이 아니다.

GCC 16을 쓰는 별도 시험 대상에서는 contracts 문법을 켜고 평가 방식을 명시해 볼 수 있다. 아래는 앞의 함수 등을 contracts-example.cpp에 저장한 뒤 목적 파일로 컴파일하는 명령 예다. -c를 사용하므로 실행 파일의 진입 함수인 main이 없어도 되는 구성이다.

g++ -std=c++26 -fcontracts \
  -fcontract-evaluation-semantic=enforce \
  -c contracts-example.cpp

GCC 옵션의 값은 표준 논의에서 쓰는 이름과 철자가 일부 다르다. ignore와 observe는 각각 ignored, observed이고 quick-enforce는 quick_enforce다. 프로젝트가 어떤 검사를 기대하는지 빌드 설정에 남겨 두면 개발용 로그 수집과 중단 동작을 서로 다른 구성으로 시험할 수 있다. 검사 모드를 적지 않은 명령만 배포하면 어느 동작을 기대했는지 코드만 보고 알기 어렵다.

반면 여러 컴파일러를 지원하는 공개 라이브러리의 헤더에 pre를 넣으면 소비자도 그 문법을 읽을 수 있어야 한다. 라이브러리 개발자가 GCC에서 컴파일에 성공해도, 정식 Clang을 쓰는 소비자는 같은 헤더를 처리하지 못한다. 따라서 지원 컴파일러를 제한할 수 있는 내부 실험과 기존 사용자 모두에게 공개할 인터페이스 변경은 다른 선택이다.

조건부 컴파일로 문법을 가리는 방법도 있다. GCC 지원표에는 __cpp_contracts 기능 매크로가 제시돼 있다. 다만 문법 지원을 나타내는 매크로가 실행 때 어떤 평가 모드를 썼는지까지 보증하지는 않는다. contracts를 사용할 수 없는 구성에서도 입력 검증과 기존 테스트가 유지되도록 설계해야 라이브러리의 동작이 컴파일러 선택에 따라 뜻밖에 달라지지 않는다.

다음 투표에 올릴 초안은 아직 정해지지 않았다

ISO/IEC 지침 §2.6.4에는 부결 뒤 수정한 DIS를 8주 투표에 부치거나, 위원회 초안을 다시 회람하거나, 기술 명세 초안으로 진행하는 등의 경로가 있다. 프로젝트 취소도 위원회 결정이 필요한 선택지다. 이 목록은 가능한 절차이며 C++26의 다음 경로가 이미 선택됐다는 발표가 아니다.

표준화 참여자 마테우시 푸시의 결과 해설도 다음 논의에서 반대 의견을 다루는 과정을 설명한다. contracts를 유지하자는 쪽의 제안과 삭제하자는 쪽의 제안은 그 논의의 입력이다. 재투표 기간이 8주라는 규정만으로 새 발행 월이나 ‘C++27’이라는 이름을 계산할 수는 없다.

현재 제품이 C++23 등 기존 표준을 대상으로 빌드된다면 이번 부결만으로 코드를 바꿀 이유는 없다. C++26 시험 코드는 컴파일러 버전과 검사 모드를 고정해 결과를 비교할 수 있다. 다중 컴파일러용 라이브러리는 지원 범위를 지키면서 실험 코드부터 분리하는 편이 현실적이다. 다음 초안에서 contracts가 어떻게 바뀌는지에 따라 그 실험을 유지하거나 수정할 근거가 생긴다.