Lumiera  0.pre.03
»edit your freedom«
/Werk/devel/lumi/src/lib/hetero-data.hpp
auto h1 = Front::build (1,2.3);
using Cons1 = Front::Chain<bool,string>;
auto b2 = Cons1::build (true, "Ψ");
b2.linkInto(h1);
auto& [d1,d2,d3,d4] = Cons1::recast(h1);
CHECK (d1 == 1);
CHECK (d2 == 2.3);
CHECK (d3 == true);
CHECK (d4 == "Ψ");
Cons1::AccessorFor<string> get4;
CHECK (get4(h1) == "Ψ");
Todo:
WIP-WIP this is the draft of a design sketch regarding the render node network, which seems to be still pretty much in flux as of 12/2024
See also
HeteroData_test
steam::engine::TurnoutSystem (use case)
/*
HETERO-DATA.hpp - handle chain of heterogeneous data blocks
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
#ifndef LIB_HETERO_DATA_H
#define LIB_HETERO_DATA_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include <utility>
#include <tuple>
namespace lib {
template<typename...DATA>
class HeteroData;
struct StorageLoc
{
StorageLoc* next{nullptr};
};
template<size_t seg, typename...DATA>
struct StorageFrame
: protected StorageLoc
, std::tuple<DATA...>
{
using Tuple = std::tuple<DATA...>;
using Tuple::tuple;
template<typename SPEC>
void linkInto (HeteroData<SPEC>&);
template<size_t slot> auto& get() noexcept { return std::get<slot>(*this); }
template<typename X> auto& get() noexcept { return std::get<X>(*this); }
};
template<size_t seg, typename...DATA, class TAIL>
class HeteroData<meta::Node<StorageFrame<seg, DATA...>,TAIL>>
: StorageFrame<seg, DATA...>
{
using _Self = HeteroData;
using _Tail = HeteroData<TAIL>;
using Tuple = std::tuple<DATA...>;
using Frame = StorageFrame<seg, DATA...>;
static constexpr size_t localSiz = sizeof...(DATA);
template<size_t slot>
static constexpr bool isLocal = slot < localSiz;
template<size_t slot>
using PickType = std::conditional_t<isLocal<slot>, std::tuple_element<slot,Tuple>
, typename _Tail::template PickType<slot-localSiz>>;
// need to use this helper to prevent eager evaluation on Elm_t<i>
_Tail&
accessTail()
{
REQUIRE (Frame::next, "HeteroData storage logic broken: follow-up extent not yet allocated");
return * reinterpret_cast<_Tail*> (Frame::next);
}
template<typename...SPEC>
static _Self&
recast (HeteroData<SPEC...>& frontChain)
{
return reinterpret_cast<_Self&> (frontChain);
}
template<typename...SPEC>
static _Self const&
recast (HeteroData<SPEC...> const& frontChain)
{
return reinterpret_cast<_Self const&> (frontChain);
}
template<typename...XX>
friend class HeteroData;
using Frame::Frame;
public:
HeteroData() = default;
static constexpr size_t
size()
{
return localSiz + _Tail::size();
}
template<size_t slot>
using Elm_t = typename PickType<slot>::type;
template<size_t slot>
Elm_t<slot>&
get() noexcept
{
static_assert (slot < size(), "HeteroData access index beyond defined data");
if constexpr (slot < localSiz)
return std::get<slot> (*this);
else
return accessTail().template get<slot-localSiz>();
}
template<size_t slot>
Elm_t<slot> const&
get() const noexcept
{
return const_cast<HeteroData*>(this)->get<slot>();
}
template<size_t slot>
struct Accessor
{
using Type = Elm_t<slot>;
template<class SPEC>
static Type&
get (HeteroData<SPEC>& frontEnd)
{
auto& fullChain = _Self::recast (frontEnd);
return fullChain.template get<slot>();
}
template<typename HH>
Type& operator() (HH& frontEnd) const { return Accessor::get(frontEnd); }
};
template<typename...VALS>
struct Chain
{
using Segments = meta::Node<Frame,TAIL>; // ◁———this type describes current chain structure
using NewFrame = StorageFrame<meta::count<Segments>::value, VALS...>;
using ChainType = HeteroData<typename meta::Append<Segments,NewFrame>::List>;
// ...and this would be the extended chain structure
template<typename...INIT>
static NewFrame
build (INIT&& ...initArgs)
{
return {initArgs ...}; // Note: NewFrame is non-copyable
}
template<class HET>
static auto&
recast (HET& frontChain)
{
return ChainType::recast (frontChain);
}
template<typename...XVALS>
using ChainExtent = typename ChainType::template Chain<XVALS...>;
template<size_t slot>
using Accessor = typename ChainType::template Accessor<_Self::size()+slot>;
template<typename X>
using AccessorFor = Accessor<meta::indexOfType<X,VALS...>()>;
};
};
template<>
class HeteroData<meta::NullType>
{
public:
static size_t constexpr size() { return 0; }
template<size_t>
using Elm_t = void;
template<size_t>
using PickType = void;
};
/*************************************************************************/
template<typename...DATA>
class HeteroData
: public HeteroData<meta::Node<StorageFrame<0, DATA...>, meta::NullType>>
{
using _Front = HeteroData<meta::Node<StorageFrame<0, DATA...>, meta::NullType>>;
public:
using NewFrame = typename _Front::Frame;
using ChainType = _Front;
template<typename...INIT>
static _Front
build (INIT&& ...initArgs)
{
return {initArgs ...};
}
};
namespace {
inline StorageLoc*&
checkedTraversal (size_t segments, StorageLoc* last)
{
REQUIRE(last);
while (segments and last->next)
{
last = last->next;
--segments;
}
ASSERT (last->next == nullptr and segments == 1
,"Failure to attach new data segment to HeteroData: "
"assumed type structure does not match real connectivity, "
"end-of-chain encountered with %d type segment(s) remaining"
, segments);
return last->next;
}
}//(End)helper
template<size_t seg, typename...DATA>
template<typename SPEC>
inline void
StorageFrame<seg,DATA...>::linkInto (HeteroData<SPEC>& prefixChain)
{
StorageLoc* firstSeg = reinterpret_cast<StorageLoc*> (&prefixChain);
StorageLoc*& lastLink = checkedTraversal (seg, firstSeg);
ENSURE (lastLink == nullptr);
lastLink = this;
}
}// namespace lib
namespace std { // Specialisation to support C++ »Tuple Protocol« and structured bindings.
template<typename...DATA>
struct tuple_size<lib::HeteroData<DATA...> >
: std::integral_constant<std::size_t, lib::HeteroData<DATA...>::size()>
{ };
template<size_t I, typename...DATA>
struct tuple_element<I, lib::HeteroData<DATA...> >
{
using type = typename lib::HeteroData<DATA...>::template Elm_t<I>;
};
template<size_t I>
struct tuple_element<I, lib::HeteroData<lib::meta::NullType> >
{
static_assert ("accessing element-type of an empty HeteroData block");
};
// Note: deliberately NOT providing a free get<i> function.
// Overload resolution would fail, since it attempts to instantiate std::get<i>(tuple) as a candidate,
// which triggers an assertion failure when using an index valid only for the full chain, not the base tuple
template<size_t seg, typename...DATA>
struct tuple_size<lib::StorageFrame<seg,DATA...> >
: std::tuple_size<typename lib::StorageFrame<seg,DATA...>::Tuple>
{ };
template<size_t I, size_t seg, typename...DATA>
struct tuple_element<I, lib::StorageFrame<seg,DATA...> >
: std::tuple_element<I, typename lib::StorageFrame<seg,DATA...>::Tuple>
{ };
// no need to define an overload for std::get<i>
// (other than a template specialisation, it will use base-type conversion to std::tuple on its argument;
}// namespace std
#endif /*LIB_HETERO_DATA_H*/