Это шаблон, с которым я столкнулся, возясь с кортежами и кортежами типов для проекта ECS, над которым я работаю. Это вдохновлено boost::hana, прекрасной библиотекой метапрограммирования шаблонов, которая обеспечивает доступ к кортежам с помощью operator[].

std::tuple и std::get

std::tuple это круто. С++ 11 поставляется с множеством полезных функций, а std::tuple позволяет писать много универсального программного кода. Однако доступ к членам кортежа возможен только через std::get, которая не является функцией-членом, и есть очень веская причина, по которой она не может быть функцией-членом. Предположим следующий иллюстративный случай:

Причина, по которой закомментированный код не работает, заключается в том, что когда get является зависимым именем, использование явного экземпляра шаблона требует объявления его как шаблона, что очень менее привлекательно для пользователя. Итак, как это исправить? Первое, на что следует обратить внимание, это то, что индекс (или тип) доступа всегда известен во время компиляции, поэтому вместо явного создания экземпляра параметра можно передать тип, соответствующий индексу, в качестве аргумента. Современные библиотеки метапрограммирования шаблонов используют эту технику для улучшения синтаксиса. Итак, как передать индекс как тип? std::integral_constant спешит на помощь. Обратите внимание на следующую реализацию:

Здесь get вызывается с синтаксисом обычного члена, поэтому нет необходимости в ключевом слове template, несмотря на то, что на самом деле это метод шаблона. Этот метод позволяет заменить элемент get на operator[], что позволяет использовать более знакомый унифицированный шаблон доступа. Однако это все еще выглядит не так хорошо. idx_c<> слишком много печатать для чего-то, что используется так часто, а методы шаблонов в классах шаблонов могут замедлить компиляцию и заполнить ваш терминал строками непонятных сообщений об ошибках, когда что-то пойдет не так. Над чем еще мы можем работать? При использовании вариационных шаблонов классический подход заключается в использовании рекурсии с наследованием, что может дополнительно расширить сообщения об ошибках вашего компилятора, но более продвинутые реализации могут использовать «параллельный» метод. По сути, такой подход позволяет сократить шаблонизацию методов доступа на один уровень. Рассмотрим следующую реализацию:

kv_map — это помеченный держатель, который предоставляет единственный нешаблон operator[], который принимает объект класса K, который работает как ключ, и возвращает сохраненное значение типа V. Опираясь на сопоставление ключ-значение, мы создаем простой map, и наш кортеж можно рассматривать как сопоставление std::integral_constant с заданным типом. tuple_base — это уровень косвенности для перехода от списка типов к последовательности kv_pairs, а tuple обеспечивает наш фактический интерфейс. Несмотря на то, что здесь задействован некоторый уровень косвенности, когда известен тип кортежа, механизмы завершения кода смогут перечислить все доступные operator[] для экземпляра. Это связано с компромиссом: при использовании шаблонного метода создание экземпляров выполняется лениво, поэтому, если вы никогда не вызываете оператор для какого-либо типа, он не будет создан; с этим методом все методы компилируются для каждого экземпляра kv_pair. Это может привести к увеличению размера двоичных файлов, но в большинстве случаев LTO должен иметь возможность удалить из двоичных файлов все неиспользуемые методы. Что касается лишнего набора текста на idx_c? boost::hana использует шаблон оператора числового литерала, чтобы преобразовать целое число в интегральную_константу, например 42_c. Один из способов реализации буквального оператора _c заключается в следующем:

это дает доступ к кортежу с t[0_c], что довольно разумно. интересно, если t оказывается не кортежем, а любым типом, который принимает std::size_t в качестве параметра, он будет работать через неявное преобразование целочисленной константы.

Я нашел этот шаблон полезным, и, надеюсь, он может быть полезным для вас, читатель.

[],