Translation-unit-local entities (seit C++20)
Translation-unit-local (TU-local) entities werden eingeführt, um zu verhindern, dass Entitäten, die lokal sein sollen (nicht in einer anderen Translation Unit verwendet), in anderen Translation Units offengelegt und verwendet werden.
Ein Beispiel aus Understanding C++ Modules: Part 2 veranschaulicht das Problem der fehlenden Einschränkung von Offenlegungen.
// Module unit without TU-local constraints export module Foo; import <iostream>; namespace { class LolWatchThis { // internal linkage, cannot be exported static void say_hello() { std::cout << "Hello, everyone!\n"; } }; } export LolWatchThis lolwut() { // LolWatchThis is exposed as return type return LolWatchThis(); }
// main.cpp import Foo; int main() { auto evil = lolwut(); // 'evil' has type of 'LolWatchThis' decltype(evil)::say_hello(); // definition of 'LolWatchThis' is not internal anymore }
Inhalt |
[bearbeiten] TU-lokale Entitäten
Eine Entität ist TU-lokal, wenn sie
- ein Typ, eine Funktion, eine Variable oder eine Template ist, die
- einen Namen mit interner Bindung hat, oder
- keinen Namen mit Bindung hat und innerhalb der Definition einer TU-lokalen Entität deklariert oder durch einen Lambda-Ausdruck eingeführt wird,
- ein Typ ohne Namen ist, der außerhalb eines Klassenspezifizierers, eines Funktionskörpers oder eines Initialisierers definiert ist oder durch einen definierenden Typspezifizierer (Typspezifizierer, Klassenspezifizierer oder Aufzählungsspezifizierer) eingeführt wird, der nur zur Deklaration von TU-lokalen Entitäten verwendet wird,
- eine Spezialisierung eines TU-lokalen Templates ist, oder
- eine Spezialisierung eines Templates mit einem TU-lokalen Template-Argument ist, oder
- eine Spezialisierung eines Templates ist, dessen (möglicherweise instanziierte) Deklaration eine Offenlegung ist (weiter unten definiert).
// 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; } };
| Dieser Abschnitt ist unvollständig Grund: Fehlende Beispiele für Regeln #1.2, #2 und #5 |
Ein Wert oder Objekt ist TU-lokal, wenn entweder
- es sich um eine TU-lokale Funktion handelt oder ein Zeiger darauf ist, oder das Objekt, das mit einer TU-lokalen Variablen assoziiert ist, oder
- es sich um ein Objekt vom Klassen- oder Array-Typ handelt und eines seiner Subobjekte oder eines der Objekte oder Funktionen, auf die seine nicht-statischen Datenmember vom Referenztyp verweisen, TU-lokal ist und in konstanten Ausdrücken verwendbar ist.
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
[bearbeiten] Offenlegungen
Eine Deklaration D benennt eine Entität E, wenn
- D einen Lambda-Ausdruck enthält, dessen Closur-Typ E ist,
- E keine Funktion oder kein Funktionstemplate ist und D einen Id-Ausdruck, Typspezifizierer, Nested-Name-Spezifizierer, Template-Namen oder Konzept-Namen enthält, der sich auf E bezieht, oder
- E eine Funktion oder ein Funktionstemplate ist und D einen Ausdruck enthält, der sich auf E bezieht, oder einen Id-Ausdruck, der auf eine Menge von Überladungen verweist, die E enthält.
// 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) )
Eine Deklaration ist eine Offenlegung, wenn sie entweder eine TU-lokale Entität benennt, wobei Folgendes ignoriert wird:
- der Funktionskörper für eine nicht-inline-Funktion oder ein nicht-inline-Funktionstemplate (aber nicht der abgeleitete Rückgabetyp für eine (möglicherweise instanziierte) Definition einer Funktion mit deklariertem Rückgabetyp, die einen Platzhaltertyp verwendet),
- der Initialisierer für eine Variable oder ein Variablentemplate (aber nicht der Typ der Variablen),
- `friend`-Deklarationen in einer Klassendefinition, und
- jede Referenz auf ein nicht-volatiles const-Objekt oder eine Referenz mit interner oder keiner Bindung, initialisiert mit einem konstanten Ausdruck, der keine ODR-Nutzung ist,
oder eine `constexpr`-Variable definiert, die mit einem TU-lokalen Wert initialisiert wird.
| Dieser Abschnitt ist unvollständig Grund: Fehlende Beispiele für Offenlegungen |
[bearbeiten] TU-lokale Einschränkungen
Wenn eine (möglicherweise instanziierte) Deklaration einer nicht-TU-lokalen Entität oder eine Deduktionshilfe für eine solche in einer Modulschnittstelleneinheit (außerhalb des `private-module-fragment`, falls vorhanden) oder einer Modulpartition eine Offenlegung ist, ist das Programm fehlerhaft. Eine solche Deklaration in jedem anderen Kontext ist veraltet.
Wenn eine Deklaration, die in einer Translation Unit erscheint, eine TU-lokale Entität benennt, die in einer anderen Translation Unit deklariert wurde, die keine Header Unit ist, ist das Programm fehlerhaft. Eine für eine Template-Spezialisierung instanziierte Deklaration erscheint am Instanziierungspunkt der Spezialisierung.
| Dieser Abschnitt ist unvollständig Grund: Fehlende Beispiele für Einschränkungen |
[bearbeiten] Beispiel
Translation Unit #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
Translation Unit #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 }
| Dieser Abschnitt ist unvollständig Grund: Beispiele sind zu komplex, bessere Anordnung nötig |