상충하는 선언

상충하는 선언 (Conflicting declarations)

같은 개체(entity)를 두 번 선언할 때 규칙이 헷갈릴 때가 있어요. C++은 두 선언이 언제 같은 개체를 (재)도입하는지, 언제 상충하는지를 정밀하게 정의해요. 이 페이지에서 그 규칙을 살펴볼게요. 달리 명시되지 않으면, 두 선언은 같은 개체를 (재)도입할 수 없어요. 그런 선언이 존재하면 프로그램은 ill-formed예요.

출처: cppreference

본문

대응하는 선언 (Corresponding declarations)

두 선언이 같은 이름을 (재)도입하고 둘 다 생성자를 선언하거나 둘 다 소멸자를 선언하면 대응(correspond) 한다고 해요. 단 다음 경우는 제외해요.

  • 둘 중 하나가 using 선언이거나,
  • 하나가 타입(typedef 이름이 아닌)을 선언하고 다른 하나가 변수, 익명 공용체가 아닌 비정적 데이터 멤버, 열거자, 함수, 함수 템플릿을 선언하거나,
  • 각각이 함수나 함수 템플릿을 선언하는데 대응하는 오버로드를 선언하지 않는 경우.

대응하는 함수 오버로드 (Corresponding function overloads)

두 함수 선언이 다음 조건을 모두 만족하는 함수를 선언하면 대응하는 오버로드 를 선언한다고 해요.

  • 명시적 객체 매개변수의 타입을 제외하고(since C++23) 같은 매개변수 타입 목록(parameter-type-list)을 가져요.
  • 동등한 후행 requires 절(있다면, friend 선언 제외)을 가져요. (since C++20)
  • 둘 다 비정적 멤버 함수라면 다음 요구사항 중 하나를 추가로 만족해야 해요.
    • 정확히 하나가 ref-한정자 없이 암시적 객체 멤버 함수이고, 최상위 참조를 제거한 후 객체 매개변수 타입이 같음. (since C++23)
    • 객체 매개변수 타입이 같음.

대응하는 함수 템플릿 오버로드 (Corresponding function template overloads)

두 함수 템플릿 선언이 다음 조건을 모두 만족하는 함수 템플릿을 선언하면 대응하는 오버로드 를 선언한다고 해요.

  • 템플릿 매개변수 목록의 길이가 같아요.
  • 대응하는 템플릿 매개변수가 동등(equivalent)해요.
  • 명시적 객체 매개변수의 타입을 제외하고(since C++23) 동등한 매개변수 타입 목록을 가져요.
  • 동등한 반환 타입을 가져요.
  • 대응하는 템플릿 매개변수가 둘 다 제약 없이 선언되거나, 둘 다 동등한 제약으로 선언돼요.
  • 동등한 후행 requires 절(있다면)을 가져요. (since C++20)
  • 둘 다 비정적 멤버 함수 템플릿이라면 다음 요구사항 중 하나를 추가로 만족해야 해요.
    • 정확히 하나가 ref-한정자 없이 암시적 객체 멤버 함수 템플릿이고, 모든 참조를 제거한 후 객체 매개변수 타입이 동등함. (since C++23)
    • 객체 매개변수 타입이 동등함.
struct A
{
    friend void c();   // #1
};

struct B
{
    friend void c() {} // corresponds to, and defines, #1
};

typedef int Int;

enum E : int { a };

void f(int);   // #2
void f(Int) {} // defines #2
void f(E) {}   // OK, another overload

struct X
{
    static void f();
    void f() const;   // error: redeclaration
    
    void g();
    void g() const;   // OK
    void g() &;       // error: redeclaration
    
    void h(this X&, int);
    void h(int) &&;   // OK, another overload
    
    void j(this const X&);
    void j() const &; // error: redeclaration
    
    void k();
    void k(this X&);  // error: redeclaration
};

같은 개체의 여러 선언 (Multiple declarations of the same entity)

선언의 이름이 _이고 다음 중 하나를 선언하면 그 선언은 이름 독립(name-independent) 이에요.

  • 자동 저장 기간을 가진 변수
  • 저장 클래스 지정자가 없고 네임스페이스 스코프에 있지 않은 구조화 바인딩
  • 결과 바인딩(result binding)
  • 초기화식을 가진 람다 캡처가 도입한 변수
  • 익명 공용체가 아닌 비정적 데이터 멤버 (since C++26)

달리 명시되지 않으면, 두 개체 선언은 다음 조건을 모두 만족하면 같은 개체를 선언한다고 해요. 이때 무명 타입의 선언은 링키지 목적상 그 typedef 이름과 열거형 이름을 도입하는 것으로 간주해요(있다면).

  • 대응해요.
  • 함수 매개변수 스코프나 템플릿 매개변수 스코프가 아닌 같은 대상 스코프를 가져요.
  • 둘 다 이름 독립 선언이 아니에요. (since C++26)
  • 다음 조건 중 하나를 만족해요.
    • 같은 번역 단위에 나타나요.
    • 둘 다 모듈 링키지를 가진 이름을 선언하고 같은 모듈에 붙어 있어요. (since C++20)
    • 둘 다 외부 링키지를 가진 이름을 선언해요.

개체나 typedef 이름 X의 선언은, X의 다른 선언이 그 선언에서 도달 가능하면 X재선언(redeclaration) 이고, 그렇지 않으면 X의 첫 번째 선언이에요.

제한 (Restrictions)

개체 E의 두 선언 중 어떤 것이든 아래 대응하는 제한을 위반하면 프로그램은 ill-formed예요.

  • 하나가 E를 변수로 선언하면, 다른 하나도 E를 같은 타입의 변수로 선언해야 해요.
  • 하나가 E를 함수로 선언하면, 다른 하나도 E를 같은 타입의 함수로 선언해야 해요.
  • 하나가 E를 열거자로 선언하면, 다른 하나도 E를 열거자로 선언해야 해요.
  • 하나가 E를 네임스페이스로 선언하면, 다른 하나도 E를 네임스페이스로 선언해야 해요.
  • 하나가 E를 클래스 타입으로 선언하면, 다른 하나도 E를 같은 종류의 클래스 타입으로 선언해야 해요.
  • 하나가 E를 열거형 타입으로 선언하면, 다른 하나도 E를 열거형 타입으로 선언해야 해요.
  • 하나가 E를 클래스 템플릿으로 선언하면, 다른 하나도 E를 동등한 템플릿 매개변수 목록을 가진 같은 종류의 클래스 템플릿으로 선언해야 해요(함수 템플릿 오버로딩 참고).
  • 하나가 E를 함수 템플릿으로 선언하면, 다른 하나도 E를 동등한 템플릿 매개변수 목록과 타입을 가진 함수 템플릿으로 선언해야 해요.
  • 하나가 E를 별칭 템플릿으로 선언하면, 다른 하나도 E를 동등한 템플릿 매개변수 목록과 type-id를 가진 별칭 템플릿으로 선언해야 해요. (since C++11)
  • 하나가 E를 (부분 전문화인) 변수 템플릿으로 선언하면, 다른 하나도 E를 동등한 템플릿 매개변수 목록과 타입을 가진 (부분 전문화인) 변수 템플릿으로 선언해야 해요. (since C++14)
  • 하나가 E를 개념(concept)으로 선언하면, 다른 하나도 E를 개념으로 선언해야 해요. (since C++20)

타입은 모든 타입 조정(그동안 typedef는 정의로 대체됨) 이후에 비교돼요. 배열 객체에 대한 선언은 주요(major) 배열 경계의 유무로 달라지는 배열 타입을 지정할 수 있어요. 어느 선언도 다른 선언에서 도달할 수 없다면 진단이 필요하지 않아요.

void g();      // #1
void g(int);   // OK, different entity from #1 (they do not correspond)
int g();       // Error: same entity as #1 with different type

void h();      // #2
namespace h {} // Error: same entity as #2, but not a function

내부 링키지를 가진 이름을 선언하는 선언 H가, 다른 번역 단위 U의 선언 D보다 앞서면서 U에 나타났다면 D와 같은 개체를 선언할 것이라면, 프로그램은 ill-formed예요.

잠재적으로 상충하는 선언 (Potentially-conflicting declarations)

두 선언이 대응하지만 서로 다른 개체를 선언하면 잠재적으로 상충(potentially conflict) 한다고 해요.

어떤 스코프에서 이름이 잠재적으로 상충하는 두 선언 AB에 묶이고, B가 이름 독립이 아니고(since C++26), AB보다 앞서면 프로그램은 ill-formed예요.

void f()
{
    int x, y;
    void x(); // Error: different entity for x
    int y;    // Error: redefinition
}

enum { f };   // Error: different entity for ::f

namespace A {}
namespace B = A;
namespace B = A; // OK, no effect
namespace B = B; // OK, no effect
namespace A = B; // OK, no effect
namespace B {}   // Error: different entity for B

void g()
{
    int _;
    _ = 0; // OK
    int _; // OK since C++26, name-independent declaration
    _ = 0; // Error: two non-function declarations in the lookup set
}

void h ()
{
    int _;        // #1
    _ ++;         // OK
    static int _; // Error: conflicts with #1 because
                  // static variables are not name-independent
}

참고 (Notes)

피처 테스트 매크로 (Feature-test macro)

매크로 표준 피처
__cpp_placeholder_variables 202306L (C++26) 이름 없는 멋진 플레이스홀더(_)

결함 보고 (Defect reports)

다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.

DR 적용 대상 발표된 동작 올바른 동작
CWG 279 (P1787R6) C++98 링키지 목적의 typedef 이름을 가진 무명 클래스나 열거형을 재선언할 수 있는지 불명확했음 재선언할 수 있음
CWG 338 (P1787R6) C++98 링키지 목적의 이름으로 열거자를 가진 무명 열거형을 재선언할 수 있는지 불명확했음 재선언할 수 있음
CWG 1884 (P1787R6) C++98 같은 개체의 여러 선언에 적용되는 제한이 불명확했음 명확히 함