lumiera_/src/lib/meta/tuple-record-init.hpp

216 lines
7.9 KiB
C++

/*
TUPLE-RECORD-INIT.hpp - adapter to construct tuple components from GenNode entries
Copyright (C) Lumiera.org
2016, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @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 in 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"
namespace lib {
namespace meta {
namespace { // implementation details...
using lib::Variant;
using lib::diff::Rec;
using lib::diff::GenNode;
using lib::diff::DataValues;
namespace error = lumiera::error;
/** 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 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
: boost::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 = typename 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 = typename Pick<Types<TYPES...>, i>::Type;
template<size_t i>
struct Access
{
Rec const& values;
operator TargetType<i> ()
{
return GenNodeAccessor<TargetType<i>> (values.child(i));
}
};
};
}} // namespace lib::meta
#endif /*LIB_META_TUPLE_RECORD_INIT_H*/