Namespaces
Variants

Translation-unit-local entities (since C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Les entités locales à l'unité de traduction (TU-locales) sont introduites pour empêcher que des entités censées être locales (non utilisées dans toute autre unité de traduction) soient exposées et utilisées dans d'autres unités de traduction.

Un exemple tiré de Understanding C++ Modules: Part 2 illustre le problème de ne pas contraindre les expositions :

// Module unit sans contraintes locales à l'unité de traduction
export module Foo;
import <iostream>;
namespace
{
   class LolWatchThis {        // liaison interne, ne peut pas être exportée
       static void say_hello()
       {
           std::cout << "Hello, everyone!\n";
       }
   };
}
export LolWatchThis lolwut() { // LolWatchThis est exposé comme type de retour
    return LolWatchThis();
}
// main.cpp
import Foo;
int main()
{
    auto evil = lolwut();        // 'evil' a le type 'LolWatchThis'
    decltype(evil)::say_hello(); // la définition de 'LolWatchThis' n'est plus interne
}

Table des matières

Entités locales à l'unité de traduction

Une entité est TU-local si elle est

  1. un type, fonction, variable ou modèle qui
    1. possède un nom ayant une liaison interne , ou
    2. ne possède pas de nom avec liaison et est déclaré, ou introduit par une expression lambda , dans la définition d'une entité locale à l'unité de traduction,
  2. un type sans nom qui est défini en dehors d'un spécificateur de classe , corps de fonction ou initialiseur, ou est introduit par un spécificateur de type définissant (spécificateur de type, spécificateur de classe ou spécificateur d'énumération) utilisé pour déclarer uniquement des entités locales à l'unité de traduction,
  3. une spécialisation d'un modèle local à l'unité de traduction,
  4. une spécialisation d'un modèle avec tout argument de modèle local à l'unité de traduction, ou
  5. une spécialisation d'un modèle dont la déclaration (éventuellement instanciée) constitue une exposition (définie ci-dessous).
// Entités locales à l'unité de traduction avec liaison interne
namespace { // tous les noms déclarés dans un espace de noms sans nom ont une liaison interne
    int tul_var = 1;                          // variable locale à l'unité de traduction
    int tul_func() { return 1; }              // fonction locale à l'unité de traduction
    struct tul_type { int mem; };             // type (classe) local à l'unité de traduction
}
template<typename T>
static int tul_func_temp() { return 1; }      // template local à l'unité de traduction
// Spécialisation de template locale à l'unité de traduction
template<>
static int tul_func_temp<int>() { return 3; } // spécialisation locale à l'unité de traduction
// spécialisation de template avec argument de template local à l'unité de traduction
template <> struct std::hash<tul_type> {      // spécialisation locale à l'unité de traduction
    std::size_t operator()(const tul_type& t) const { return 4u; }
};

Une valeur ou un objet est TU-local si l'une des conditions suivantes est remplie

  1. il s'agit, ou pointe vers, une fonction locale à l'unité de traduction ou l'objet associé à une variable locale à l'unité de traduction, ou
  2. il s'agit d'un objet de type classe ou tableau et l'un de ses sous-objets ou l'un des objets ou fonctions vers lesquels pointent ses membres de données non statiques de type référence est local à l'unité de traduction et est utilisable dans des expressions constantes .
static int tul_var = 1;             // Variable locale à l'unité de traduction
static int tul_func() { return 1; } // Fonction locale à l'unité de traduction
int* tul_var_ptr = &tul_var;        // Locale à l'unité de traduction : pointeur vers une variable locale à l'unité de traduction
int (* tul_func_ptr)() = &tul_func; // Locale à l'unité de traduction : pointeur vers une fonction locale à l'unité de traduction
constexpr static int tul_const = 1; // Variable locale à l'unité de traduction utilisable dans des expressions constantes
int tul_arr[] = { tul_const };      // Locale à l'unité de traduction : tableau d'objets constexpr locaux à l'unité de traduction
struct tul_class { int mem; };
tul_class tul_obj{tul_const};       // Locale à l'unité de traduction : possède un membre objet constexpr local à l'unité de traduction

Expositions

Une déclaration D nomme une entité E si

  1. D contient une expression lambda dont le type de fermeture est E,
  2. E n'est pas une fonction ou un modèle de fonction et D contient une expression-id, un spécificateur de type, un spécificateur de nom imbriqué, un nom de modèle ou un nom de concept désignant E, ou
  3. E est une fonction ou un modèle de fonction et D contient une expression qui nomme E ou une expression-id qui fait référence à un ensemble de surcharges contenant E.
// dénomination des lambda
auto x = [] {}; // nomme decltype(x)
// dénomination des non-fonctions (template)
int y1 = 1;                      // nomme y1 (expression-id)
struct y2 { int mem; };
y2 y2_obj{1};                    // nomme y2 (spécificateur-de-type)
struct y3 { int mem_func(); };
int y3::mem_func() { return 0; } // nomme y3 (spécificateur-de-nom-imbriqué)
template<typename T> int y4 = 1;
int var = y4<y2>;                // nomme y4 (nom-de-template)
template<typename T> concept y5 = true;
template<typename T> void func(T&&) requires y5<T>; // nomme y5 (nom-de-concept)
// dénomination des fonctions (template)
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); // nomme z1
int val2 = z2(0); // nomme z2 ( int z2(int) )

Une déclaration est une exposition si elle nomme soit une entité locale à l'unité de traduction, en ignorant

  1. le corps de fonction pour une fonction non inline ou un modèle de fonction (mais pas le type de retour déduit pour une définition (éventuellement instanciée) d'une fonction avec un type de retour déclaré qui utilise un type de substitution ),
  2. l'initialisateur pour une variable ou un modèle de variable (mais pas le type de la variable),
  3. les déclarations friend dans une définition de classe, et
  4. toute référence à un objet const non volatile ou référence avec liaison interne ou sans liaison initialisée avec une expression constante qui n'est pas une odr-use ,

ou définit une variable constexpr initialisée avec une valeur locale à l'unité de traduction.

Contraintes locales à l'unité de traduction

Si une (éventuellement instanciée) déclaration de, ou un guide de déduction pour, une entité non-locale à l'unité de traduction dans une unité d'interface de module (en dehors du fragment privé du module, le cas échéant) ou une partition de module constitue une exposition, le programme est mal formé. Une telle déclaration dans tout autre contexte est dépréciée.

Si une déclaration qui apparaît dans une unité de traduction nomme une entité locale à l'unité de traduction déclarée dans une autre unité de traduction qui n'est pas une unité d'en-tête, le programme est mal formé. Une déclaration instanciée pour une spécialisation de modèle apparaît au point d'instanciation de la spécialisation.

Exemple

Unité de traduction #1 :

export module A;
static void f() {}
inline void it() { f(); }         // erreur : exposition de f
static inline void its() { f(); } // OK
template<int> void g() { its(); } // OK
template void g<0>();
decltype(f) *fp;                             // erreur : f (bien que son type ne le soit pas) est local à l'unité de traduction
auto &fr = f;                                // OK
constexpr auto &fr2 = fr;                    // erreur : exposition de f
constexpr static auto fp2 = fr;              // OK
struct S { void (&ref)(); } s{f};            // OK : la valeur est locale à l'unité de traduction
constexpr extern struct W { S &s; } wrap{s}; // OK : la valeur n'est pas locale à l'unité de traduction
static auto x = []{ f(); }; // OK
auto x2 = x;                // erreur : le type de fermeture est local à l'unité de traduction
int y = ([]{ f(); }(), 0);  // erreur : le type de fermeture n'est pas local à l'unité de traduction
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, mais une spécialisation pourrait être une exposition

Unité de traduction #2 :

module A;
void other()
{
    g<0>();                  // OK : la spécialisation est explicitement instanciée
    g<1>();                  // erreur : l'instanciation utilise son TU-local
    h(N::A{});               // erreur : l'ensemble de surcharge contient N::adl(int) TU-local
    h(0);                    // OK : appelle adl(double)
    adl(N::A{});             // OK ; N::adl(int) non trouvé, appelle N::adl(N::A)
    fr();                    // OK : appelle f
    constexpr auto ptr = fr; // erreur : fr n'est pas utilisable dans les expressions constantes ici
}