상충하는 선언
상충하는 선언 (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) 한다고 해요.
어떤 스코프에서 이름이 잠재적으로 상충하는 두 선언 A와 B에 묶이고, B가 이름 독립이 아니고(since C++26), A가 B보다 앞서면 프로그램은 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 | 같은 개체의 여러 선언에 적용되는 제한이 불명확했음 | 명확히 함 |