LUMIERA.clone/src/lib/meta/tuple-record-init.hpp
Ichthyostega 20f3252892 Upgrade: down with typename!!
Yet another chainsaw massacre.

One of the most obnoxious annoyances with C++ metaprogramming
is the need to insert `typename` and `template` qualifiers into
most definitions, to help the compiler to cope with the syntax,
which is not context-free.

The recent standards adds several clarifications, so that most
of these qualifiers are redundant now, at least at places where
it is unambiguously clear that only a type can be given.

GCC already supports most of these relaxing rules
(Clang unfortunately lags way behind with support of newer language features...)
2025-07-06 01:19:08 +02:00

223 lines
8.4 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
TUPLE-RECORD-INIT.hpp - adapter to construct tuple components from GenNode entries
Copyright (C)
2016, 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.
*/
/** @file tuple-record-init.hpp
** Specialised adapter to consume a record of GenNode entries to build a tuple.
** This is a complement to lib::meta::TupleConstructor to deal with arguments
** passed in our "External Tree Description" form. This is relevant for structural
** diff and the invocation of actions driven by messages via the UI-Bus.
**
** In those cases, a sequence of arguments will be passed within a run-time sequential
** container, as a sequence of GenNode entries. The latter are variant records,
** which means they can hold any of a small collection of basic types, like
** numbers, strings, time values etc. So we have to face two challenges here.
** - the sequence of types in a std::tuple is fixed at compile time, as is
** the sequence of constructor arguments to build a given tuple type.
** Thus we need a compile-time iteration over a run-time sequence container
** - the concrete type of the given initialisation value is opaque, hidden within
** the GenNode variant record. And especially this means that the concrete type
** is known only at runtime. But we need to generate the construction code at
** compile time. The remedy here is to use double dispatch, i.e. to build a
** trampoline table with all the basically possible conversion paths for one
** specific type within the target tuple. For example, if one component of our
** tuple is a `long`, we'll provide a conversion path for the case when the
** GenNode holds a `short`, an `int`, and -- depending on the platform -- when
** it holds a `int64_t` (please recall that long and int are the same type on
** 32bit platforms!). When encountering any other of the possible types within
** GenNode (e.g. `string` or `TimeSpan`), a type mismatch exception is raised.
**
** @see control::CommandDef usage example
** @see TupleHelper_test
** @see typelist.hpp
** @see function.hpp
** @see generator.hpp
**
*/
#ifndef LIB_META_TUPLE_RECORD_INIT_H
#define LIB_META_TUPLE_RECORD_INIT_H
#include "lib/meta/tuple-helper.hpp"
#include "lib/meta/typelist-manip.hpp"
#include "lib/meta/typeseq-util.hpp"
#include "lib/meta/generator.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/meta/trait.hpp"
// GCC > 13 warns at class definition when a new overload shadows an inherited virtual function.
// While theoretically correct, in practice any call will be dispatched through the base interface,
// which in turn is also generated by metaprogramming from a type list.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Woverloaded-virtual\"")
namespace lib {
namespace meta {
namespace error = lumiera::error;
namespace { // implementation details...
using lib::Variant;
using lib::diff::Rec;
using lib::diff::GenNode;
using lib::diff::DataValues;
/** the visitor type our converter is based on */
using DataCapPredicate = Variant<DataValues>::Predicate;
/** @internal
* specialised Accessor to retrieve a given target type
* from a run-time typed, opaque GenNode element. Since GenNode
* may hold some value from a (\ref lib::diff::DataValues small
* and fixed selection of types), we prepare a converter function
* for each sensible conversion path to the given `TAR` target type.
* This converter will placement-construct a target value in a buffer
* reserved within this Accessor object.
* @warning template bloat: we get a converter for each possible conversion
* for each instantiation of this template, irrespective if we actually
* need it or not. The price is payed per distinct type used within some
* target tuple; we get a VTable and some conversion functions for each.
*/
template<typename TAR>
struct GenNodeAccessor
: util::NonCopyable
{
template<typename TY>
struct allow_Conversion
: __and_<is_constructible<TAR, TY const&>
,__not_<is_narrowingInit<typename Strip<TY>::TypePlain
,typename Strip<TAR>::TypePlain>>
>
{ };
using SupportedSourceTypes = Filter<DataValues::List, allow_Conversion>::List;
struct ConverterBase
: DataCapPredicate
{
char buffer[sizeof(TAR)];
};
template<typename TY, class BA>
class Converter
: public BA
{
virtual bool
handle(TY const& srcElm)
{
new(&(BA::buffer)) TAR{srcElm};
return true;
};
};
using ConversionBuffer = InstantiateChained< SupportedSourceTypes // for each of those types...
, Converter // instantiate the Converter template
, ConverterBase // and use this as common base class.
>; // RESULT: for each type we get a handle(TY)
ConversionBuffer converter_;
TAR&
accessTargetElement()
{
return *reinterpret_cast<TAR*> (&converter_.buffer);
}
public:
GenNodeAccessor (GenNode const& node)
: converter_()
{
if (not node.data.accept (converter_))
throw error::Invalid ("Unable to build «" + util::typeStr<TAR>()
+"» element from " + string(node)
,error::LUMIERA_ERROR_WRONG_TYPE);
}
/** @note RAII: when the ctor succeeds, we know the target element was built */
~GenNodeAccessor()
{
accessTargetElement().~TAR();
}
operator TAR ()
{
return accessTargetElement();
}
};
}//(End)implementation details
/**
* Concrete specialisation to build a std::tuple from a sequence
* of GenNode elements, with run-time type compatibility check.
* @remarks intention is to use this within (\ref TupleConstructor),
* as will happen automatically when invoking (\ref buildTuple() )
* with a `Record<GenNode>` as argument. The embedded template
* `Access<i>` will be initialised with (a reference) to the
* source record, followed by an attempt to convert it to the
* individual member type at position i within the target tuple.
* @throws error::Invalid when the number of types within the target
* exceeds the number of children within the source record.
* \ref LUMIERA_ERROR_INDEX_BOUNDS
* @throws error::Invalid when some source GenNode element
* can not be sensibly converted to the corresponding
* type in the target tuple.
* \ref LUMIERA_ERROR_WRONG_TYPE
*/
template<typename...TYPES>
struct ElementExtractor<lib::diff::Rec, std::tuple<TYPES...>>
{
template<size_t i>
using TargetType = Pick<Types<TYPES...>, i>::Type;
template<size_t i>
struct Access
{
Rec const& values;
operator TargetType<i> ()
{
using util::toString;
if (values.childSize() <= i)
throw error::Logic ("Attempt to init the " + toString(sizeof...(TYPES))
+" element " + util::typeStr<std::tuple<TYPES...>>()
+" from an Rec<GenNode> with only " + toString(values.childSize())
+" child elements: " + toString(values)
,error::LUMIERA_ERROR_WRONG_TYPE);
return GenNodeAccessor<TargetType<i>> (values.child(i));
}
};
};
}} // namespace lib::meta
#endif /*LIB_META_TUPLE_RECORD_INIT_H*/