I'm creating a tree of templated typenames. I am using tuples for the trees structure. Every Node
in the tree that is not a leaf
will contain a tuple of typenames containing other Nodes
and Leaves
. The leaves will contain a size
and an offset
representing their position within the tuple (relative to root node and based on the size
of the leaf nodes before it). Essentially all values will be known at compile time, I would like to use the tuple as a map of sorts. The end result will look something like so:
Simple enough, I create a variadic template note and unpack the result into a tuple for the nodes
template<typename... Elements>
struct Node {
std::tuple<Elements...> elements;
};
But the catch is that I would like both the Nodes
and the leaves
to contain a template argument for their offset position within the tuple, that way I can access "Leaf 4" for example from Node 1 by using std::get<0>(std::get<1>(Node0))
, and from within "Node 2" I could access its elements each of which would still know their offset within the "grander tuple". Programmatically this would look something like so (albeit below does not account for the offset part):
template<uint16_t offset, uint16_t size>
struct Leaf {
constexpr uint16_t GetOffset() {return offset; }
constexpr uint16_t GetSize() {return size; }
};
template<uint16_t offset, typename... Elements>
struct Node {
std::tuple<Elements...> elements; //Does not setup proper offsets
constexpr uint16_t GetOffset() { return offset; }
};
As you can see I have a tuple of elements but I would need to "Recast" them into new templates using a new offset based on the size of the leaves prior. So, along the lines of "Recasting" my next idea was this:
template<uint16_t offset, uint16_t size>
struct Leaf {
static constexpr uint16_t GetOffset() {return offset; }
static constexpr uint16_t GetSize() {return size; }
//New ability to Recast
template<uint16_t newOffset>
struct Recast {
typedef Leaf<newOffset, size> type;
};
};
template<uint16_t offset, typename... Elements>
struct Node {
static constexpr uint16_t GetSize() {
return (Elements::GetSize() + ...);
}
static constexpr auto GetElements() {
uint16_t size = 0;
//Using the comma operator to increment size before recasting using it
return std::tuple<(Elements::template Recast<(size+= Elements::GetSize(), size)>::type)...>
}
static constexpr auto elements = GetElements();
static constexpr uint16_t GetOffset() { return offset; }
//New ability to Recast
template<uint16_t newOffset>
struct Recast {
typedef Node<newOffset, Elements...> type;
};
};
But now I'm running into issues where size
can't be used as a constant expression and even if it was the increment would come before it was used as an offset by the recast due to the order of operations using the comma operator. Long story short, how can I make this vision a reality, is their a tweak I can make to the existing setup or is there a different tool that I should be using that would help simplify this whole thing?
To potentially clear things up I am using template arguments to store the offset because that way I can ensure that it can be evaluated at compile time and so that the data memory storage of such values is unnecessary. Basically I want to make sure that if these values are going to be inlined anyways that they aren't also taking up space on the stack or heap. If there is another way to accomplish something similar that would be perfect as well.
Sorry if I don't quite get the exact logic how the code work, but you might try this:
struct Node {
⋮
⋮
template<uint16_t ... I, uint16_t ... J>
static constexpr auto GetElements_helper(std::integer_sequence<uint16_t, I...>, std::integer_sequence<uint16_t, J...>)
{
constexpr std::array arr{I...};
return std::tuple<typename Recast<std::accumulate(arr.begin(), arr.begin() + J + 1, uint16_t {})>::type...>{};
}
static constexpr auto GetElements()
{
return GetElements_helper(std::integer_sequence<uint16_t, Elements::GetSize()...>{}, std::make_integer_sequence<uint16_t, sizeof...(Elements)>{});
}
⋮
⋮
}