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

Перевод-единично-локальные (TU-локальные) сущности вводятся для предотвращения экспонирования и использования в других единицах трансляции сущностей, которые должны быть локальными (не использоваться в других единицах трансляции).

Пример из Understanding C++ Modules: Part 2 иллюстрирует проблему отсутствия ограничений на экспортируемые элементы:

// Модульная единица без ограничений, локальных для единицы трансляции
export module Foo;
import <iostream>;
namespace
{
   class LolWatchThis {        // внутренняя компоновка, не может быть экспортирована
       static void say_hello()
       {
           std::cout << "Hello, everyone!\n";
       }
   };
}
export LolWatchThis lolwut() { // LolWatchThis раскрывается как возвращаемый тип
    return LolWatchThis();
}
// main.cpp
import Foo;
int main()
{
    auto evil = lolwut();        // 'evil' имеет тип 'LolWatchThis'
    decltype(evil)::say_hello(); // определение 'LolWatchThis' больше не является внутренним
}

Содержание

TU-локальные сущности

Сущность называется локальной для единицы трансляции если она

  1. тип, функция, переменная или шаблон, которые
    1. имеют имя с внутренней линковкой , или
    2. не имеют имени с линковкой и объявлены, или введены с помощью лямбда-выражения , внутри определения TU-локальной сущности,
  2. тип без имени, который определен вне спецификатора класса , тела функции или инициализатора, или введен определяющим спецификатором типа (спецификатором типа, спецификатором класса или спецификатором перечисления), который используется для объявления только TU-локальных сущностей,
  3. специализация TU-локального шаблона,
  4. специализация шаблона с любым TU-локальным аргументом шаблона, или
  5. специализация шаблона, чье (возможно, инстанцированное) объявление является экспозицией (определено ниже).
// Сущности, локальные для единицы трансляции, с внутренним связыванием
namespace { // все имена, объявленные в безымянном пространстве имён, имеют внутреннее связывание
    int tul_var = 1;                          // локальная для TU переменная
    int tul_func() { return 1; }              // локальная для TU функция
    struct tul_type { int mem; };             // локальный для TU (классовый) тип
}
template<typename T>
static int tul_func_temp() { return 1; }      // локальный для TU шаблон
// Специализация шаблона, локальная для TU
template<>
static int tul_func_temp<int>() { return 3; } // локальная для TU специализация
// специализация шаблона с локальным для TU аргументом шаблона
template <> struct std::hash<tul_type> {      // локальная для TU специализация
    std::size_t operator()(const tul_type& t) const { return 4u; }
};

Значение или объект является TU-локальным если выполняется одно из условий:

  1. это, или является указателем на, TU-локальную функцию или объект, связанный с TU-локальной переменной, или
  2. это объект класса или массива и любой из его подобъектов или любой из объектов или функций, на которые ссылаются его нестатические члены данных ссылочного типа, является TU-локальным и пригодным для использования в константных выражениях .
static int tul_var = 1;             // TU-локальная переменная
static int tul_func() { return 1; } // TU-локальная функция
int* tul_var_ptr = &tul_var;        // TU-локальная: указатель на TU-локальную переменную
int (* tul_func_ptr)() = &tul_func; // TU-локальная: указатель на TU-локальную функцию
constexpr static int tul_const = 1; // TU-локальная переменная, используемая в константных выражениях
int tul_arr[] = { tul_const };      // TU-локальный: массив из constexpr TU-локального объекта
struct tul_class { int mem; };
tul_class tul_obj{tul_const};       // TU-локальный: содержит член constexpr TU-локального объекта

Экспозиции

Объявление D именует сущность E, если

  1. D содержит лямбда-выражение, тип замыкания которого является E,
  2. E не является функцией или шаблоном функции, и D содержит id-выражение, спецификатор типа, спецификатор вложенного имени, имя шаблона или имя концепции, обозначающее E, или
  3. E является функцией или шаблоном функции, и D содержит выражение, которое именует E, или id-выражение, которое ссылается на набор перегрузок, содержащий E.
// именование лямбда-выражений
auto x = [] {}; // именует decltype(x)
// именование не-функций (шаблонов)
int y1 = 1;                      // именует y1 (id-выражение)
struct y2 { int mem; };
y2 y2_obj{1};                    // именует y2 (спецификатор типа)
struct y3 { int mem_func(); };
int y3::mem_func() { return 0; } // именует y3 (спецификатор вложенного имени)
template<typename T> int y4 = 1;
int var = y4<y2>;                // именует y4 (имя шаблона)
template<typename T> concept y5 = true;
template<typename T> void func(T&&) requires y5<T>; // именует y5 (имя концепции)
// именование функций (шаблонов)
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); // именует z1
int val2 = z2(0); // именует z2 ( int z2(int) )

Объявление является экспозицией , если оно либо называет сущность, локальную для единицы трансляции, игнорируя

  1. тело функции для не встроенной функции или шаблона функции (но не выведенный возвращаемый тип для (возможно инстанцированного) определения функции с объявленным возвращаемым типом, использующим тип-заполнитель ),
  2. инициализатор для переменной или шаблона переменной (но не тип переменной),
  3. объявления friend в определении класса, и
  4. любая ссылка на не-volatile const объект или ссылку с внутренней или отсутствующей линковкой, инициализированную константным выражением, которое не является odr-использованием ,

или определяет переменную constexpr, инициализированную значением, локальным для единицы трансляции.

Ограничения в пределах единицы трансляции

Если (возможно, инстанцированное) объявление или руководство по выводу для не-TU-локальной сущности в модульном интерфейсном блоке (вне приватного модульного фрагмента, если таковой имеется) или модульной партиции является экспозицией, программа является некорректной. Такое объявление в любом другом контексте устарело.

Если объявление, которое появляется в одной единице трансляции, именует локальную для единицы трансляции сущность, объявленную в другой единице трансляции, которая не является заголовочной единицей, программа является некорректной. Объявление, инстанцированное для специализации шаблона, появляется в точке инстанцирования специализации.

Пример

Единица трансляции #1:

export module A;
static void f() {}
inline void it() { f(); }         // ошибка: является раскрытием f
static inline void its() { f(); } // OK
template<int> void g() { its(); } // OK
template void g<0>();
decltype(f) *fp;                             // ошибка: f (хотя не его тип) является локальным для единицы трансляции
auto &fr = f;                                // OK
constexpr auto &fr2 = fr;                    // ошибка: является раскрытием f
constexpr static auto fp2 = fr;              // OK
struct S { void (&ref)(); } s{f};            // OK: значение является локальным для единицы трансляции
constexpr extern struct W { S &s; } wrap{s}; // OK: значение не является локальным для единицы трансляции
static auto x = []{ f(); }; // OK
auto x2 = x;                // ошибка: тип замыкания является локальным для единицы трансляции
int y = ([]{ f(); }(), 0);  // ошибка: тип замыкания не является локальным для единицы трансляции
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, но специализация может быть раскрытием

Единица трансляции #2:

module A;
void other()
{
    g<0>();                  // OK: специализация явно инстанцирована
    g<1>();                  // ошибка: инстанцирование использует TU-локальный its
    h(N::A{});               // ошибка: набор перегрузок содержит TU-локальный N::adl(int)
    h(0);                    // OK: вызывает adl(double)
    adl(N::A{});             // OK; N::adl(int) не найден, вызывает N::adl(N::A)
    fr();                    // OK: вызывает f
    constexpr auto ptr = fr; // ошибка: fr не может использоваться в константных выражениях здесь
}