TU-로컬 엔티티

TU-로컬 엔티티 (Translation-unit-local entities)

모듈(module)은 어떤 엔티티가 다른 번역 단위에서 쓰여서는 안 되는데, 그런 게 실수로 노출되어 밖에서 쓰여 버리는 문제를 막기 위한 장치가 필요해요. TU-로컬(TU-local) 엔티티는 바로 그 개념이에요 — "이 엔티티는 이 번역 단위 안에서만 쓰여야 한다"는 걸 컴파일러가 검사하게 해 주죠. 특히 C++ 모듈을 다룰 때 중요한 규칙이에요.

출처: cppreference

본문

TU-로컬 엔티티

다음 중 하나에 해당하면 그 엔티티는 TU-로컬이에요.

  1. 다음을 만족하는 타입·함수·변수·템플릿
    • 내부 링키지(internal linkage)를 가진 이름을 가지거나,
    • 링키지를 가진 이름이 없으면서 TU-로컬 엔티티의 정의 안에서 선언되거나 람다 표현식으로 도입된 것
  2. 클래스 지정자, 함수 본문, 또는 초기화기 밖에서 정의된 이름 없는 타입이나, TU-로컬 엔티티만 선언하는 데 쓰이는 defining-type-specifier(type-specifier, class-specifier, enum-specifier)로 도입된 이름 없는 타입
  3. TU-로컬 템플릿의 특수화
  4. TU-로컬 템플릿 인자를 가진 템플릿의 특수화
  5. (인스턴스화됐을 수도 있는) 선언이 아래에서 설명할 노출(exposure) 인 템플릿의 특수화
// TU-local entities with internal linkage
namespace { // all names declared in unnamed namespace have internal linkage
    int tul_var = 1;                          // TU-local variable
    int tul_func() { return 1; }              // TU-local function
    struct tul_type { int mem; };             // TU-local (class) type
}
template<typename T>
static int tul_func_temp() { return 1; }      // TU-local template

// TU-local template specialization
template<>
static int tul_func_temp<int>() { return 3; } // TU-local specialization

// template specialization with TU-local template argument
template <> struct std::hash<tul_type> {      // TU-local specialization
    std::size_t operator()(const tul_type& t) const { return 4u; }
};
이 절은 불완전해요
이유: 규칙 #1.2, #2, #5의 예시가 빠져 있음

다음 중 하나에 해당하면 그 값이나 객체가 TU-로컬이에요.

  1. TU-로컬 함수 또는 TU-로컬 변수에 연관된 객체(또는 그에 대한 포인터)인 경우
  2. 클래스·배열 타입의 객체이며, 그 하위 객체(subobject) 중 하나나 참조 타입 비정적 데이터 멤버가 가리키는 객체·함수 중 하나가 TU-로컬이면서 상수 표현식에서 쓸 수 있는 경우
static int tul_var = 1;             // TU-local variable
static int tul_func() { return 1; } // TU-local function

int* tul_var_ptr = &tul_var;        // TU-local: pointer to TU-local variable
int (* tul_func_ptr)() = &tul_func; // TU-local: pointer to TU-local function

constexpr static int tul_const = 1; // TU-local variable usable in constant expressions
int tul_arr[] = { tul_const };      // TU-local: array of constexpr TU-local object 
struct tul_class { int mem; };
tul_class tul_obj{tul_const};       // TU-local: has member constexpr TU-local object

노출 (Exposures)

선언 D가 어떤 엔티티 E를 가리킨다는 것은 다음 중 하나를 뜻해요.

  1. D가 클로저 타입이 E인 람다 표현식을 포함하는 경우
  2. E가 함수나 함수 템플릿이 아니고, D가 E를 나타내는 id-표현식, type-specifier, nested-name-specifier, template-name, concept-name을 포함하는 경우
  3. E가 함수나 함수 템플릿이고, D가 E를 가리키는 표현식이나 E를 포함한 오버로드 집합을 가리키는 id-표현식을 포함하는 경우
// lambda naming
auto x = [] {}; // names decltype(x)

// non-function (template) naming
int y1 = 1;                      // names y1 (id-expression)
struct y2 { int mem; };
y2 y2_obj{1};                    // names y2 (type-specifier)
struct y3 { int mem_func(); };
int y3::mem_func() { return 0; } // names y3 (nested-name-specifier)
template<typename T> int y4 = 1;
int var = y4<y2>;                // names y4 (template-name)
template<typename T> concept y5 = true;
template<typename T> void func(T&&) requires y5<T>; // names y5 (concept-name)

// function (template) naming
int z1(int arg)    { std::cout << "no overload"; return 0; }
int z2(int arg)    { std::cout << "overload 1";  return 1; }
int z2(double arg) { std::cout << "overload 2";  return 2; }

int val1 = z1(0); // names z1
int val2 = z2(0); // names z2 ( int z2(int) )

어떤 선언은 TU-로컬 엔티티를 가리키면서 다음 항목들을 무시하면 노출이에요.

  1. inline이 아닌 함수나 함수 템플릿의 함수 본문(단, 자리 표시자 타입으로 선언된 반환 타입을 쓰는 함수 정의의 (인스턴스화됐을 수도 있는) 추론된 반환 타입은 제외)
  2. 변수나 변수 템플릿의 초기화기(변수의 타입은 제외)
  3. 클래스 정의 안의 friend 선언
  4. odr-use가 아닌, 상수 표현식으로 초기화된 내부 링키지 또는 링키지가 없는 비휘발성 const 객체·참조에 대한 모든 참조

또는 TU-로컬 값으로 초기화된 constexpr 변수를 정의하는 선언도 노출이에요.

이 절은 불완전해요
이유: 노출에 대한 예시가 빠져 있음

TU-로컬 제약 (TU-local constraints)

모듈 인터페이스 단위(있는 경우 private-module-fragment 밖)나 모듈 파티션에 있는, TU-로컬이 아닌 엔티티의 (인스턴스화됐을 수도 있는) 선언 또는 그에 대한 추론 가이드(deduction guide)가 노출이면 프로그램은 ill-formed예요. 다른 문맥에서의 그런 선언은 deprecate돼요.

한 번역 단위에 나타난 선언이, 헤더 단위가 아닌 다른 번역 단위에 선언된 TU-로컬 엔티티를 가리키면 프로그램은 ill-formed예요. 템플릿 특수화를 위해 인스턴스화된 선언은 그 특수화의 인스턴스화 지점에 나타난 것으로 간주돼요.

이 절은 불완전해요
이유: 제약에 대한 예시가 빠져 있음

예제

번역 단위 #1:

export module A;
static void f() {}
inline void it() { f(); }         // error: is an exposure of f
static inline void its() { f(); } // OK
template<int> void g() { its(); } // OK
template void g<0>();

decltype(f) *fp;                             // error: f (though not its type) is TU-local
auto &fr = f;                                // OK
constexpr auto &fr2 = fr;                    // error: is an exposure of f
constexpr static auto fp2 = fr;              // OK
struct S { void (&ref)(); } s{f};            // OK: value is TU-local
constexpr extern struct W { S &s; } wrap{s}; // OK: value is not TU-local

static auto x = []{ f(); }; // OK
auto x2 = x;                // error: the closure type is TU-local
int y = ([]{ f(); }(), 0);  // error: the closure type is not TU-local
int y2 = (x, 0);            // OK

namespace N
{
    struct A {};
    void adl(A);
    static void adl(int);
}
void adl(double);

inline void h(auto x) { adl(x); } // OK, but a specialization might be an exposure

번역 단위 #2:

module A;
void other()
{
    g<0>();                  // OK: specialization is explicitly instantiated
    g<1>();                  // error: instantiation uses TU-local its
    h(N::A{});               // error: overload set contains TU-local N::adl(int)
    h(0);                    // OK: calls adl(double)
    adl(N::A{});             // OK; N::adl(int) not found, calls N::adl(N::A)
    fr();                    // OK: calls f
    constexpr auto ptr = fr; // error: fr is not usable in constant expressions here
}
이 절은 불완전해요
이유: 예시가 너무 복잡해서 더 나은 정리가 필요함

더 알아보기

  • 내부 링키지와 저장 기간, 링키지의 종류는 링키지 문서에서 더 자세히 다뤄요.
  • 모듈 인터페이스 단위와 모듈 파티션, export의 규칙은 모듈 문서에서 확인할 수 있어요.
  • cppreference의 TU-로컬 엔티티 원문에서 결함 보고(defect report) 기록을 더 볼 수 있어요.