шаблоны и пакеты параметровtemplates and parameter packs
Возможность объявить весь пакет параметров шаблона другом класса одной строкой — friend Ts...; — вместо отдельного friend-объявления на каждый тип пакета.The ability to grant friendship to an entire template parameter pack with a single line — friend Ts...; — instead of a separate friend declaration for every type in the pack.
P2893 расширяет синтаксис friend так, чтобы он принимал целый пакет параметров шаблона типов: template<typename... Ts> class Container { friend Ts...; }; предоставляет дружественный доступ каждому типу из пакета Ts одной строкой. До этого friend T; работал только для одного конкретного типа — написать friend Ts...; для пакета было синтаксически невозможно.P2893 extends friend syntax to accept an entire type template parameter pack: template<typename... Ts> class Container { friend Ts...; }; grants friend access to every type in the pack Ts with one line. Before this, friend T; only worked for one specific type — writing friend Ts...; for a pack simply had no syntax.
class Logger;
class Metrics;
class Tracer;
class Registry {
friend class Logger; // три отдельных объявления —// three separate declarations —
friend class Metrics; // не масштабируется на переменный набор типов// doesn't scale to a variable set of types
friend class Tracer;
private:
void internal_register();
};
template<typename... Ts>
class Registry {
private:
void internal_register();
friend Ts...; // каждый тип пакета Ts становится другом// every type in the Ts pack becomes a friend
};
// Logger, Metrics и Tracer теперь могут вызывать internal_register()// Logger, Metrics, and Tracer can now call internal_register()
using AppRegistry = Registry<Logger, Metrics, Tracer>;
До C++26 предоставление доступа переменному набору типов, известному только через пакет параметров шаблона, не имело прямого синтаксиса вообще:Before C++26, granting access to a variable set of types known only through a template parameter pack had no direct syntax at all:
Если набор типов, которым нужно дать доступ, приходил через template<typename... Ts>, не было способа написать friend Ts...; — приходилось либо разворачивать список типов вручную заранее (что противоречит самой идее вариативного шаблона), либо искать обходной путь.If the set of types needing access arrived via template<typename... Ts>, there was no way to write friend Ts...; — you either had to unroll the type list by hand ahead of time (defeating the point of a variadic template), or find a workaround.
Практика решала это "passkey"-идиомой, промежуточным CRTP-базовым классом, от которого наследовался каждый друженный тип, или — что случалось чаще, чем хотелось бы — просто объявлением нужных членов публичными, ослабляя инкапсуляцию только потому, что вариативная дружба не стоила связанного с ней шаблонного бойлерплейта.In practice this was solved with a "passkey" idiom, an intermediate CRTP base class each friended type inherited from, or — more often than one would like — simply making the needed members public, weakening encapsulation purely because variadic friendship wasn't worth the associated template boilerplate.
Вариативные шаблоны классов (обобщённый диспетчер visitor-паттерна, tuple-подобный тип, которому нужно открыть доступ фиксированному, но шаблонному набору вспомогательных типов) уже выражают переменность через пакет во всех остальных местах кода — за исключением friend-объявлений. P2893 закрывает этот последний разрыв.Variadic class templates (a generic visitor-pattern dispatcher, a tuple-like type that needs to grant access to a fixed but templated set of helper types) already express variability through a pack everywhere else in the code — except in friend declarations. P2893 closes that last gap.
Синтаксис — это существующее объявление friend T; (C++11, дружба с типом-параметром шаблона), расширенное на пакет: если Ts — пакет параметров шаблона типов, friend Ts...; раскрывается компилятором в набор обычных friend-объявлений, по одному на каждый элемент пакета, ровно в том порядке, в котором пакет был инстанциирован.The syntax is the existing friend T; declaration (C++11, friendship with a template type parameter) extended to a pack: if Ts is a type template parameter pack, friend Ts...; is expanded by the compiler into an ordinary set of friend declarations, one per pack element, in exactly the order the pack was instantiated with.
template<typename... Ts>
class Registry {
private:
void internal_register() { /* ... *//* ... */ }
int state_ = 0;
public:
friend Ts...; // эквивалентно friend Logger; friend Metrics; friend Tracer;// equivalent to friend Logger; friend Metrics; friend Tracer;
};
class Logger {
void on_startup(Registry<Logger, Metrics, Tracer>& r) {
r.internal_register(); // OK: Logger назван в пакете Ts// OK: Logger is named in the Ts pack
}
};
Если пакет пуст (Registry<>), friend Ts...; просто ничего не разворачивает — не ошибка, а объявление дружбы с нулём типов.If the pack is empty (Registry<>), friend Ts...; simply expands to nothing — not an error, just a friendship declaration with zero types.
Обобщённый билдер хранит частично сконструированное состояние приватно; каждый тип-шаг из пакета Steps... должен иметь доступ к нему, чтобы дописывать своё поле, не открывая состояние публично для всего остального кода.A generic builder keeps its partially-constructed state private; each step type in the Steps... pack needs access to it to fill in its own field, without exposing the state publicly to everything else.
template<typename... Steps>
class Builder {
private:
PartialState state_;
public:
friend Steps...; // каждый Step пишет напрямую в state_// every Step writes directly into state_
};
Вариативный Subject<Observers...> предоставляет доступ к приватным внутренностям уведомления только заранее известному, шаблонному набору типов-наблюдателей — без публичного API "для уведомлений", которым мог бы воспользоваться кто угодно.A variadic Subject<Observers...> grants access to private notification internals only to a known-in-advance, templated set of observer types — with no public "for notification" API that anything else could latch onto.
template<typename... Observers>
class Subject {
private:
void notify_internal(const Event& e);
friend Observers...;
};
Набор тестовых фикстур получает доступ к приватным внутренностям тестируемого класса (SUT) без публичного "для тестов" API, который иначе оставался бы постоянной лазейкой в production-сборке.A set of test fixture classes gets access to a system-under-test's private internals with no public "for testing" API that would otherwise remain a permanent loophole in the production build.
template<typename... TestFixtures>
class SUT {
private:
int internal_counter() const;
friend TestFixtures...;
};
using TestedSUT = SUT<CounterFixture, EdgeCaseFixture>;
Тот же результат до C++26 требовал промежуточного базового класса-ключа и наследования от него каждым допущенным типом — существенно больше движущихся частей ради того же итогового доступа.The same result before C++26 required an intermediate key base class, inherited from by each admitted type — significantly more moving parts for the same end result.
// до C++26: PassKey-идиома вместо friend-пакета// before C++26: PassKey idiom instead of a friend pack
class RegistryPassKey {
RegistryPassKey() = default;
friend class Logger; // всё равно перечисление вручную// still enumerated by hand
friend class Metrics;
friend class Tracer;
};
class Registry {
public:
void internal_register(RegistryPassKey); // ключ как параметр-пропуск// the key as a pass-parameter
};
Friend-пакет должен быть именно пакетом параметров шаблона типов, которые являются классами/структурами — пакет non-type-параметров или пакет, разрешающийся не в тип класса, использовать так нельзя. Спутать это с другими видами пакетов — ошибка компиляции.The friend pack must be specifically a template parameter pack of types that are classes/structs — a pack of non-type template parameters, or a pack resolving to something other than a class type, cannot be used this way. Confusing this with other kinds of packs is a compile error.
Как и любое friend-объявление, это полностью снимает инкапсуляцию для названных типов без более тонкого контроля — нельзя дать доступ только части элементов пакета, не разделив сам пакет (например, через индекс или type trait). Это грант на весь пакет целиком, либо ни на что.Like any friend declaration, this fully breaks encapsulation for the named types with no finer-grained control — you can't grant access to only some pack elements without splitting the pack itself (e.g. via an index or a trait). It's an all-or-nothing pack-wide grant.
Если один из друженных типов пакета имеет собственные производные классы, эти производные классы автоматически другом НЕ становятся — точно так же, как и при обычной дружбе в C++. Правило "friend не наследуется" не меняется от того, что дружба выдана через пакет.If one of the friended types in the pack has its own derived classes, those derived classes are NOT automatically friends too — exactly as with ordinary C++ friendship. The "friend isn't inherited" rule doesn't change just because the friendship was granted through a pack.