3

I have some tricky code that I need to initialize many objects that refer to each other. I want to allocate and construct them all at once, so I put them all in a struct.

It looks like this:

 // User code struct level1 { explicit constexpr level1(int* ptr) : ptr{ptr} {} int* ptr; }; struct level2 { constexpr level2(int* ptr_0, level1* ptr_1) : ptr_0{ptr_0}, ptr_1{ptr_1} {} int* ptr_0; level1* ptr_1; }; struct level3 { constexpr level3(int* ptr_0, level1* ptr_1, level2* ptr_2) : ptr_0{ptr_0}, ptr_1{ptr_1}, ptr_2{ptr_2} {} int* ptr_0; level1* ptr_1; level2* ptr_2; }; // Library side struct self_referential { explicit constexpr self_referential(int base_value) : a{base_value}, b{&a}, c{&a, &b}, d{&a, &b, &c} {} int a; level1 b; level2 c; level3 d; }; constexpr auto eval_result() -> int { auto const s = self_referential{9}; return *s.d.ptr_2->ptr_1->ptr; } auto main() -> int { return eval_result(); } 

This code is valid and runs: https://godbolt.org/z/sMqofdshY

Now the problem, is that I want to make self_referential a variadic template. Is there a way I can make this struct generic? I cannot change the definition of levelx as they are user side, I want to send levelx to self referential as a variadic template.

I tried with a tuple at first:

template<typename Base, typename... Ts> struct self_referential { explicit constexpr self_referential(Base b) : values{b, std::get<???>(values)...} {} std::tuple<Base, Ts...> values; }; 

I think I'm kinda breaking std::get's contract here. Also, I would kinda need a pack of pack.

I tried using inheritance, but again without much luck:

template<typename Base, typename... Ts> struct self_referential : self_referential<Ts...> { explicit constexpr self_referential(Base base) : self_referential<Ts...>{&b}, b{base} {} // oh no Base b; }; 

It looked easy at first but then I realized I don't have references to all the other b in the struct. On top of that, parents are initialized before child classes so this won't really work either.

Is there an easier way to do that? Maybe some kind of reverse of what I was doing with the inheritance?

2
  • Don't forget to delete copy-constructor or implement it to fix internal pointers. Commented Oct 9 at 8:19
  • @Jarod42 yes, I was intending to do that. Omitted the detail for simplicity. Commented Oct 9 at 12:53

6 Answers 6

3

Inheritance works like this:

template <unsigned N, typename T> struct level { level<N - 1, T> lower; level<N - 1, T> *lower_ptr; }; template <typename T> struct level<1u, T> { T *ptr; }; template <unsigned N, typename T> struct self_referential : self_referential<N - 1, T> { using base = self_referential<N - 1, T>; explicit constexpr self_referential(const T &value) : base(value), top_level{this->base::top_level, &this->base::top_level} {} level<N, T> top_level; }; template <typename T> struct self_referential<1u, T> { T data; level<1u, T> top_level; explicit constexpr self_referential(const T &value) : data(value), top_level{&data} {} }; int main() { self_referential<3u, int> instance(42); return *instance.top_level.lower.lower_ptr->ptr - 42; } 

This is just a direct transformation of your level and self_referential classes to the generic ones. I'm not sure what you want exactly.

Sign up to request clarification or add additional context in comments.

5 Comments

Maybe I needed to be slightly more specific. The definition of level cannot really change. They are gonna be the etmplate parameter to self referential. In your example, self_referential is not a variadic template so this is unfortunately not so useful for my problem.
I think that without level being a template itself, making a variadic template of any kind (up-to and including clever lambdas) is not going to work if you still have explicit level1 level2 and level_n classes. (At least not until we get C++26 reflection). Or do you intend to use level1, level2, level3 as Ts... ? That's also still not clear to me. Can you add a "I wish I had this calling syntax for self_referential" example?
> Or do you intend to use level1, level2, level3 as Ts... ? Yes. I want this: self_referential<int, level1, level2, level3> to be equivalent of the one I wrote by hand.
Could you clarify your use case for making self_referential a template like self_referential<T, Ts...>? It seems that the initialization of self_referential is tightly dependent on the specific definitions of level1, level2, and level3. What other types (besides level1, level2, or level3) might potentially be passed as template arguments?
I actually have many types coming from user code of the library, and I also have a function to initialize all of the fields given a pointer to all the previous ones. Doing so allows me to easily merge allocations and initialize everything at once, even at constexpr time.
2

You can almost directly translate your member-initializer:

 explicit constexpr self_referential(int base_value) : a{base_value}, b{&a}, c{&a, &b}, d{&a, &b, &c} {} 

To a variadic initializer that expands into something similar:

 template<std::size_t... I> explicit constexpr self_referential(int base_value, std::index_sequence<I...>) : a{base_value}, level<I>(/* Address of a + first I-1 elements */)... {} 

You can't do this with data members but you can do this with base classes (https://godbolt.org/z/4cazK1TG7):

template<typename Base, typename... Levels> struct self_referential : private std::tuple<Base>, Levels... { explicit constexpr self_referential(auto&& base_value) requires(std::is_constructible_v<Base, decltype(base_value)>) : self_referential(std::forward<decltype(base_value)>(base_value), std::make_index_sequence<sizeof...(Levels)>{}) {} private: template<typename T, std::size_t... I> explicit constexpr self_referential(T&& base_value, std::index_sequence<I...>) : std::tuple<Base>(std::forward<T>(base_value)), std::tuple_element_t<I, std::tuple<Levels...>>([&]<std::size_t... J>(std::index_sequence<J...>) { return std::tuple_element_t<I, std::tuple<Levels...>>{ std::addressof(get_level<J>())... }; }(std::make_index_sequence<I+1u>{}))... {} public: template<std::size_t I> requires(I <= sizeof...(Levels)) constexpr auto& get_level() & noexcept { if constexpr (I == 0) { return std::get<0>(*static_cast<std::tuple<Base>*>(this)); } else { return *static_cast<std::tuple_element_t<I-1u, std::tuple<Levels...>>*>(this); } } template<std::size_t I> requires(I <= sizeof...(Levels)) constexpr const auto& get_level() const& noexcept { return const_cast<self_referential&>(*this).get_level<I>(); } template<std::size_t I> requires(I <= sizeof...(Levels)) constexpr auto&& get_level() && noexcept { return std::move(get_level<I>()); } template<std::size_t I> requires(I <= sizeof...(Levels)) constexpr const auto&& get_level() const&& noexcept { return std::move(get_level<I>()); } }; // Usage constexpr auto eval_result() -> int { auto const s = self_referential<int, level1, level2, level3>{9}; return *s.get_level<3>().ptr_2->ptr_1->ptr; } static_assert(eval_result() == 9); 

Comments

1

You need some template code to achieve this (like shown in other answers), I also cleaned up the usage a bit :

#include <tuple> #include <type_traits> #include <utility> namespace nested { // Concept-like boolean variable templates for cleaner constraints template <typename T> constexpr bool is_valid_level_type = !std::is_pointer_v<T> && !std::is_reference_v<T>; template <std::size_t N> constexpr bool is_valid_level_number = N >= 1; template <std::size_t N, typename... Args> constexpr bool has_valid_constructor_args = (N == 1 && sizeof...(Args) == 0) || (N > 1 && sizeof...(Args) == 1); template <std::size_t M> constexpr bool has_parent = M > 1; template <std::size_t N, int TargetLevel> constexpr bool is_valid_ancestor = TargetLevel < N&& TargetLevel >= 1; template <std::size_t N> concept ValidLevelNumber = is_valid_level_number<N>; template <typename T, std::size_t N> struct level { static_assert(is_valid_level_type<T>, "T must not be a pointer or reference type"); static_assert(is_valid_level_number<N>, "N must be >= 1"); // Store the top-level pointer separately T* base_ptr; std::size_t depth = N; // Conditional storage for parent level pointer (only for N > 1) using storage_type = std::conditional_t<N == 1, std::tuple<>, std::tuple<level<T, N - 1>*>>; storage_type parent_ptrs; // Constructor with requires clause - only compiles when argument count matches expected for N template <typename... Args> constexpr level(T* base, Args&&... args) requires has_valid_constructor_args<N, Args...> : base_ptr(base) , parent_ptrs(std::forward<Args>(args)...) { } // Access to the value pointed to by your initial pointer constexpr T& value() const { return *base_ptr; } // Previous level accessor (only available for N > 1) template <std::size_t M = N> constexpr level<T, N - 1>& parent() const requires has_parent<M> { static_assert(has_parent<M>, "parent() cannot be called for level<1>"); return *std::get<0>(parent_ptrs); } // Get ancestor by moving up a specific number of levels from current level template <std::size_t LevelsUp> constexpr auto& get_ancestor() const requires (LevelsUp > 0 && LevelsUp < N) { if constexpr (LevelsUp == 1) { return parent(); } else { return parent().template get_ancestor<LevelsUp - 1>(); } } }; } // namespace nested struct self_referential { // some aliases to make your code look like it did before using level1 = nested::level<int, 1>; using level2 = nested::level<int, 2>; using level3 = nested::level<int, 3>; explicit constexpr self_referential(int base_value) : a{base_value} , b{&a} , c{&a, &b} , d{&a, &c} { } int a; level1 b; level2 c; level3 d; }; constexpr int eval_result() { auto const s = self_referential{9}; // I took the liberty of cleaning the syntax up a bit here :) return s.d.get_ancestor<2>().value(); } int main() { constexpr auto value = eval_result(); return value; } 

3 Comments

Hmm. This is not really useful to me as an answer as self_referential is not a template. My goal is to merge some dynamic allocation together between objects (being the levels) and self_referential is simply a container for allocation. I need self_referential to be the template and the levels untouched.
I must have missed that as an important aspect somehow
I think I could have emphasized this a bit more. I edited the question to be clearer about this
1

You can set the N+1th element of a tuple by addresses of the first N elements of the tuple, for example:

#include <tuple> template<typename Base, typename... Ts> struct self_referential { explicit constexpr self_referential(Base b) : self_referential(b, std::index_sequence_for<Ts...>{}) {} std::tuple<Base, Ts...> values; private: template<std::size_t... Is> explicit constexpr self_referential(Base b, std::index_sequence<Is...>) : values{} { std::get<0>(values) = b; ([&]<std::size_t... Js>(std::index_sequence<Js...>) { std::get<Is + 1>(values) = {&std::get<Js>(values)...}; }(std::make_index_sequence<Is + 1>{}), ...); } }; 

4 Comments

This call to std::get is undefined behaviour (See: eel.is/c++draft/basic.life#2.2 and eel.is/c++draft/res.on.objects#2 , you cannot call std::get before the lifetime of the tuple begins)
It requires now default-constructible types (that works with OP's example which uses pointers and not references).
Hmm. It uses the default constructor and in my code I don't have default constructors. However you're the only solution so far to leave levels untouched and actually make self_referential a template.
BTW, with a custom tuple, primary option would work without requiring default constructor. Issue with tuple is that order or construction is not guaranteed (and it is not the right order with regular recursive implementation).
1

You have a base type, and a sequence of level types.

The first level requires a pointer to base to construct. Each level afterwards requires a pointer to base, and then a pointer to reach previous level.

You need to construct these in order, I assume.

template<class...> struct types_t {}; template<std::size_t index, class Base, class TODO, class DONE> struct LevelStorage; // has a parent case: template<std::size_t index, class Base, class TODO0, class...TODOs, class...DONEs> struct LevelStorage<index, Base, types_t<TODO0, TODOs...>, types_t<DONEs...>> { using Parent = LevelStorage<index+1, Base, types_t<TODOs...>, types_t<DONEs..., TODO0>>; TODO0 current; Parent parent; LevelStorage( Base* base, DONEs*... doneLevels ): current{base, doneLevels...}, parent(base, doneLevels..., &current) {} }; template<std::size_t index, class Base, class...DONEs> struct LevelStorage<index, Base, types_t<>, types_t<DONEs...>> { LevelStorage( Base*, DONEs*... ){} }; template<std::size_t index, class Base, class T, class... TODOs, class... DONEs> T& get( LevelStorage<index, Base, types_t<T, TODOs...>, types_t<DONEs...>>& storage ) { return storage.current; } template<std::size_t index, class Base, class T, class... TODOs, class... DONEs> T const& get( LevelStorage<index, Base, types_t<T, TODOs...>, types_t<DONEs...>> const& storage ) { return storage.current; } template<std::size_t index, std::size_t ondex, class Base, class T, class... TODOs, class... DONEs> requires (index != ondex) auto& get( LevelStorage<ondex, Base, types_t<T, TODOs...>, types_t<DONEs...>>& storage ) { return get<index>( storage.parent ); } template<std::size_t index, std::size_t ondex, class Base, class T, class... TODOs, class... DONEs> requires (index != ondex) auto& get( LevelStorage<ondex, Base, types_t<T, TODOs...>, types_t<DONEs...>> const& storage ) { return get<index>( storage.parent ); } 

Tested with:

template<class Base, class...Ls> struct Levels { Base base; LevelStorage<0, Base, types_t<Ls...>, types_t<>> storage; Levels(Base b): base(std::move(b)), storage(&base) {} template<std::size_t I> auto& level() { return get<I>(storage); } template<std::size_t I> auto const& level() const { return get<I>(storage); } }; struct level0 { int* x; }; struct level1 { int* x; level0* l0; }; Levels<int, level0, level1> levels(7); 

Here you manually create the structs and feed it to Levels.

You can access each level struct as levels.level<0>() etc. If you go beyond the limit you'll get a compiler error (which could be made more friendly).

Each class can have arbitrary contents, so long as you can {} construct it with a pointer to base and a pointer to each "previous" level. Here I used aggregates; but you can use constructors.

Live example.

Comments

0

Since you have fixed types for levelX, you need to create some kind of mapping:

template <std::size_t N> struct levelMapping; template <> struct levelMapping<0> { using value_type = int; }; template <> struct levelMapping<1> { using value_type = level1; }; template <> struct levelMapping<2> { using value_type = level2; }; template <> struct levelMapping<3> { using value_type = level3; }; 

This is simple and clear and there is no other way to overcome this.

Now in second step you probably want to have this kind of tuple:

 std::tuple<levelMapping_t<Idx>...> a; 

Problem is how to initialize it? Note first part of this tuple is initialized differently then other parts. This means Idx have to be spitted to take this exception into account. Also you need a way to feed required parameter to each constructor.

So I've come up with this solution:

#include <tuple> #include <utility> struct level1 { int* ptr; }; struct level2 { int* ptr_0; level1* ptr_1; }; struct level3 { int* ptr_0; level1* ptr_1; level2* ptr_2; }; template <std::size_t N> struct levelMapping; template <> struct levelMapping<0> { using value_type = int; }; template <> struct levelMapping<1> { using value_type = level1; }; template <> struct levelMapping<2> { using value_type = level2; }; template <> struct levelMapping<3> { using value_type = level3; }; template <std::size_t N> using levelMapping_t = typename levelMapping<N>::value_type; template <std::size_t zero, std::size_t... Idx> struct self_referential { explicit constexpr self_referential(int base_value) : a{base_value, std::make_from_tuple<levelMapping_t<Idx>>(getPtrTuple<Idx>())...} {} template <std::size_t M> auto getPtrTuple() { return getPtrTupleImpl(std::make_index_sequence<M>{}); } template <std::size_t... Jdx> auto getPtrTupleImpl(std::integer_sequence<std::size_t, Jdx...>) { return std::tuple{&std::get<Jdx>(a)...}; } auto getLast() const { return &std::get<sizeof...(Idx)>(a); } std::tuple<levelMapping_t<zero>, levelMapping_t<Idx>...> a; }; template<typename T> struct self_referential_helper; template<std::size_t...Idx> struct self_referential_helper<std::integer_sequence<std::size_t, Idx...>> { using type = self_referential<Idx...>; }; template<std::size_t N> using make_self_referential_t = typename self_referential_helper<std::make_index_sequence<N>>::type; constexpr auto eval_result() -> int { auto const s = make_self_referential_t<4>{11}; return *s.getLast()->ptr_2->ptr_1->ptr; } auto main() -> int { return eval_result(); } 

https://godbolt.org/z/6E7n8GM3c

2 Comments

Looks nice but are we really allowed to call std::get when the tuple is not done being constructed?
AFAIR you can fetch pointer to not initialized variable, so it is fine.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.