cpp-ru/ideas

Рефлексия над 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 of get_size<T> shall meet the UnaryTypeTrait requirements (20.10.1)
with a base characteristic of integral_constant<size_t, N>, where N is the number of elements in the object sequence.

и type_of_member:

template <size_t I, ObjectSequence T> struct get_element;
All specializations of get_element<I, T> shall meet the TransformationTrait requirements
(20.10.1). The nested type named type corresponds to the Ith element Object 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