Локальные сущности единицы трансляции (начиная с C++20)
Локальные сущности единицы трансляции (TU-local - Translation unit local) вводятся для предотвращения экспорта и использования сущностей, которые должны быть локальными (не используемыми ни в одной другой единице трансляции), в других единицах трансляции.
Пример из Понимание Модулей C++: Часть 2 иллюстрирует проблему отсутствия ограничения экспорта:
// Модульная единица без ограничений TU-local
export module Foo;
import <iostream>;
namespace {
class LolWatchThis { // внутреннее связывание, не может быть экспортирована
static void say_hello() {
std::cout << "Всем привет\n";
}
};
}
export LolWatchThis lolwut() { // LolWatchThis экспортируется как возвращаемый тип
return LolWatchThis();
}
// main.cpp
import Foo;
int main() {
auto evil = lolwut(); // 'evil' имеет тип 'LolWatchThis'
decltype(evil)::say_hello(); // определение 'LolWatchThis' больше
// не является внутренним
}
TU-локальные сущности
Объект является TU-локальным, если он
- тип, функция, переменная или шаблон, который
- имеет имя с внутренним связыванием, или
- не имеет имени со связыванием и объявляется или вводится с помощью лямбда-выражения, в определении TU-локальной сущности,
- тип без имени, который определён вне спецификатора класса, тела функции или инициализатора или введён спецификатором определённого типа (спецификатором типа, спецификатором класса или спецификатором перечисления), который используется для объявления только TU-локальных сущностей,
- специализация TU-локального шаблона,
- специализация шаблона с любым TU-локальным аргументом шаблона или
- специализация шаблона, чьё объявление (возможно, инстанцированное) является экспортированием (определено ниже).
// TU-локальные сущности с внутренним связыванием
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; }
};
| Этот раздел не завершён Причина: отсутствуют примеры правил #1.2, #2 и #5 |
Значение или объект является TU-локальным, если
- это указатель на TU-локальную функцию или объект, связанный с TU-локальной переменной, или
- это объект типа класса или массива, и любой из его подобъектов или любой объект или функция, на которые ссылаются его нестатические элементы данных ссылочного типа, является 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-локальный: массив TU-локальных constexpr объектов
struct tul_class { int mem; };
tul_class tul_obj{tul_const}; // TU-локальный: имеет constexpr TU-локальный объект
// элемент
Экспозиция
Объявление D именует сущность E, если
- D содержит лямбда-выражение с типом замыкания E,
- E не является функцией или шаблоном функции, а D содержит выражение-идентификатор, спецификатор-типа, спецификатор-вложенного-имени, имя-шаблона или имя-концепта, обозначающие E, или
- E это функция или шаблон функции, а D содержит выражение, которое именует E или выражение-идентификатор, которое ссылается на набор перегрузок, содержащих E.
// лямбда именование
auto x = [] {}; // именует decltype(x)
// нефункциональное (шаблонное) именование
int y1 = 1; // именует y1 (выражение-идентификатор)
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 << "без перегрузки"; return 0; }
int z2(int arg) { std::cout << "перегрузка 1"; return 1; }
int z2(double arg) { std::cout << "перегрузка 2"; return 2; }
int val1 = z1(0); // именует z1
int val2 = z2(0); // именует z2 ( int z2(int) )
Объявление является экспозицией, если оно именует TU-локальный объект, игнорируя
- тело функции для невстраиваемой функции или шаблона функции (но не выведенный тип возвращаемого значения для (возможно, инстанцированного) определения функции с объявленным типом возвращаемого значения, использующим тип заполнитель),
- инициализатор для переменной или шаблона переменной (но не тип переменной),
- объявления друзей в определении класса и
- любая ссылка на не-volatile константный объект или ссылка с внутренним связыванием или без него, инициализированная константным выражением, которое не используется odr,
или определяет переменную constexpr, инициализированную TU-локальным значением.
| Этот раздел не завершён Причина: отсутствуют примеры для экспозиции |
TU-локальные ограничения
Если (возможно, инстанцированное) объявление или руководство по выводу для не TU-локальной сущности в единице интерфейса модуля (за пределами частного-фрагмента-модуля, если таковой имеется) или в разделе модуля представляет собой экспозицию, программа некорректна. Такое объявление в любом другом контексте не рекомендуется.
Если объявление, которое появляется в одной единице трансляции, именует 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 (хотя и не её тип)
// является TU-локальной
auto &fr = f; // OK
constexpr auto &fr2 = fr; // ошибка: экспозиция f
constexpr static auto fp2 = fr; // OK
struct S { void (&ref)(); } s{f}; // OK: значение TU-локальное
constexpr extern struct W { S &s; } wrap{s}; // OK: значение не является TU-локальным
static auto x = []{f();}; // OK
auto x2 = x; // ошибка: тип замыкания TU-локальный
int y = ([]{f();}(),0); // ошибка: тип замыкания не TU-локальный
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); } // Хорошо, но специализация может быть экспозицией
Единица трансляции #2:
module A;
void other() {
g<0>(); // OK: специализация инстанцирована явно
g<1>(); // ошибка: инстанцирование использует TU-local
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 нельзя использовать
// в константных выражениях
}
| Этот раздел не завершён Причина: примеры слишком сложны, нужно лучше организовать |