Рефлексия над pointer-to-member на этапе компиляции
pavelkryukov opened this issue · 6 comments
У поля структуры есть три базовых представления:
- порядковый номер в структуре (индекс)
- смещение относительно начала структуры (
offsetof
) - pointer-to-member
Преобразование между тремя представлениями сделать сложно. Между первым и вторым конвертирует Type Loophole, но у этого трюка есть много ограничений, низкая скорость компиляции и т. д. Третье подключить можно, но с ещё большими ухищрениями (boostorg/pfr#60).
При этом последнее представление и наиболее полно, и наиболее надёжно реализовано в C++ (и С).
Предлагается считать его основным, и попросить компилятор объявить переменные, через которые определяются все преобразования:
namespace ptm { // pointer-to-member
template<typename T>
constexpr size_t number_of_members = $$$;
template<typename T, typename R, R T::* member>
constexpr size_t index_of_member = $$$;
template<typename T, size_t N>
struct type_of_member { using type = $$$; };
template<typename T, size_t N, typename R = type_of_member<T, N>::type>
struct type_of_pointer_to_member { using type = R T::*; }
template<typename T, size_t N>
constexpr type_of_pointer_to_member<T, N> pointer_to_member = $$$;
} // namespace ptm
В результате имплементация Boost.PFR сильно сократится:
namespace details {
template<typename T, int ... N>
auto structure_tie(T& object, std::index_sequence<N...>)
{
return std::tie(*(object.*ptm::pointer_to_member<T,N>)...);
}
} // namespace details
template<typename T>
auto structure_tie(T& object)
{
return details::structure_tie(object, std::make_index_sequence<ptm::number_of_members<T>>{});
}
Отсюда же порождается имплементация #468:
namespace details {
template<typename T, int ... N>
constexpr auto tuple_of_memptrs(std::index_sequence<N...>)
{
return std::tuple(ptm::pointer_to_member<T,N>)...);
}
} // namespace details
template<typename T>
constexpr auto tuple_of_memptrs()
{
return details::tuple_of_memptrs(std::make_index_sequence<ptm::number_of_members<T>>());
}
Не самым оптимальным образом, но порождается PR0908
namespace details {
template<size_t I, typename T, typename R>
constexpr auto offsetof_impl(R T::* member)
{
static_assert(I < ptm::number_of_members<T>);
if constexpr (!std::is_same_v<R, ptm::type_of_member<T, I>>)
return offsetof_impl<I + 1>(member);
else if (ptm::pointer_to_member<T, I> != member)
return offsetof_impl<I + 1>(member);
else
return offsetof(T, ptm::pointer_to_member<T, I>);
}
} // namespace details
template<typename T, typename R>
auto offsetof_dynamic(R T::* member)
{
return details::offsetof_impl<0>(member);
}
index_of_member
может быть использован для метапрограммирования нестандартного расположения полей (SoA, упаковка и т. д.)
Reflection TS, если я его верно понял, имеет numer_of_members
:
template <ObjectSequence T> struct get_size;
All specializations ofget_size<T>
shall meet theUnaryTypeTrait
requirements (20.10.1)
with a base characteristic ofintegral_constant<size_t, N>
, whereN
is the number of elements in the object sequence.
и type_of_member
:
template <size_t I, ObjectSequence T> struct get_element;
All specializations ofget_element<I, T>
shall meet theTransformationTrait
requirements
(20.10.1). The nested type namedtype
corresponds to theI
th elementObject
in T, where
the indexing is zero-based.
Однако, вещей, похожих на pointer_to_member
и index_of_member
я не нашёл.
На самом деле эту тему переиграли и вроде как рефлексию собираются делать через constexpr-объекты, а не шаблоны. Я написал что и где, возможно надо будет пересмотреть идею (?)
https://habr.com/ru/post/598981/
А так - можно сделать вагон крутых идей, но неясно, дойдёт ли это до SG7. У меня такое ощущение, что эту лямку Andrew Sutton тянет чуть ли не в соло.
За годы немало предложений свёрнуто с формулировкой «дождитесь интроспекции/рефлексии». Нужна какая-то проактивная позиция на этот счёт; я ничего не могу предложить лучше чем принести на стол больше мотивационных примеров.
вроде как рефлексию собираются делать через constexpr-объекты, а не шаблоны
Всё же издам глас вопиющего в пустыне: зачем сходить с накатанной лыжни?
В языке уже есть шаблоно-подобные std::is_enum
, std::is_destructible
, std::is_same
...
В языке уже есть шаблоно-подобные
std::is_enum
,std::is_destructible
,std::is_same
...
Они очень плохо подходят для рефлексии. Использование шаблона его инстанцирует, инстанс при компиляции потребляет оперативную память компилятора за счёт увеличения количества элементов во внутренних структур компилятора. Структуры с большим количеством элементов тормозят т.к. замедляют поиски и ухудшают поподания в кеш. Если рефлексия инстанцирует шаблоны и одновременно работает с контейнерами хранящими инстансы - возникают сложности с инвалидацией указателей внутри компилятора
Предложение по рефлексии https://wg21.link/P2996