lumiera_/src/lib/opaque-holder.hpp

861 lines
26 KiB
C++
Raw Normal View History

/*
OPAQUE-HOLDER.hpp - buffer holding an object inline while hiding the concrete type
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **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.
2010-12-17 23:28:49 +01:00
*/
/** @file opaque-holder.hpp
** Helper allowing type erasure while holding the actual object inline.
** Controlling the actual storage of objects usually binds us to commit
** to a specific type, thus ruling out polymorphism. But sometimes, when
** we are able to control the maximum storage for a family of classes, we
** can escape this dilemma by using the type erasure pattern combined with
2009-07-04 04:35:17 +02:00
** an inline buffer holding an object of the concrete subclass. Typically,
** this situation arises when dealing with functor objects.
**
** # Managed opaque placement buffer
**
** These templates help with building custom objects and wrappers based on
2010-11-19 05:01:43 +01:00
** this pattern: lib::InPlaceAnyHolder provides a buffer for target objects
** and controls access through a two-layer capsule; while the outer container
** exposes a neutral interface, the inner container keeps track of the actual
** type by means of a vtable. OpaqueHolder is built on top of InPlaceAnyHolder
** additionally to support a "common base interface" and re-access of the
** embedded object through this interface. For this to work, all of the
** stored types need to be derived from this common base interface.
** OpaqueHolder then may be even used like a smart-ptr, exposing this
** base interface. To the contrary, InPlaceAnyHolder has lesser requirements
** on the types to be stored within. It can be configured with policy classes
** to control the re-access; when using InPlaceAnyHolder_unrelatedTypes
** the individual types to be stored need not be related in any way, but
** of course this rules out anything beyond re-accessing the embedded object
** by knowing it's exact type. Generally speaking, re-accessing the concrete
** object requires knowledge of the actual type, similar to boost::any
2010-11-19 05:01:43 +01:00
** (but contrary to OpaqueHolder the latter uses heap storage).
**
** # Lightweight passively managed opaque holder buffer
**
2010-11-19 05:01:43 +01:00
** As a supplement, a more lightweight implementation is provided as
** lib::InPlaceBuffer, requiring just the object storage and lacking the
** ability to track the actual type of the embedded object, and the buffer
** can not be empty with this model -- which turns out to be adequate in
** most usage scenarios. This kind of lightweight "inline buffer" can even
** be exposed on API through a lib::PlantingHandle, allowing an arbitrary
** client to plant an likewise opaque implementation subclass into the
** buffer, as long as the storage size constraint is observed.
**
2009-07-04 04:35:17 +02:00
** Using this approach is bound to specific stipulations regarding the
** properties of the contained object and the kind of access needed.
** When, to the contrary, the contained types are \em not related
** and you need to re-discover their concrete type, then maybe
** a visitor or variant record might be a better solution.
**
** TICKET #1204 : proper alignment verified 10/2019
**
** @see opaque-holder-test.cpp
** @see function-erasure.hpp usage example
** @see variant.hpp
*/
#ifndef LIB_OPAQUE_HOLDER_H
#define LIB_OPAQUE_HOLDER_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/access-casted.hpp"
#include "lib/meta/util.hpp"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>
#include <type_traits>
#include <utility>
namespace lib {
namespace error = lumiera::error;
using LERR_(BOTTOM_VALUE);
using LERR_(WRONG_TYPE);
using util::isSameObject;
using util::isSameAdr;
using util::unConst;
namespace { // implementation helpers...
using lib::meta::enable_if;
using lib::meta::disable_if;
using std::is_constructible;
template<typename X>
enable_if< is_constructible<bool,X>,
bool >
validitySelfCheck (X const& boolConvertible)
{
return bool(boolConvertible);
}
template<typename X>
disable_if< is_constructible<bool,X>,
bool >
validitySelfCheck (X const&)
{
return true; // just pass if this type doesn't provide a validity check...
}
}
/* ==== Policy classes controlling re-Access ==== */
/**
* Standard policy for accessing the contents via
* a common base class interface. Using this policy
* causes static or dynamic casts or direct conversion
* to be employed as appropriate.
*/
template<class BA>
struct InPlaceAnyHolder_useCommonBase
{
2025-06-07 23:59:57 +02:00
using Base = BA;
template<class SUB>
static Base*
convert2base (SUB& obj)
{
SUB* oPtr = &obj;
BA* asBase = util::AccessCasted<BA*>::access (oPtr);
if (asBase)
return asBase;
throw error::Logic ("Unable to convert concrete object to Base interface"
, LERR_(WRONG_TYPE)
);
}
};
/**
* Alternative policy for accessing the contents without
* a common interface; use this policy if the intention is
* to use OpaqueHolder with a family of similar classes,
* _without requiring all of them_ to be derived from
* a _common base_ class. (E.g. std::function objects).
* In this case, the "Base" type will be defined to void*
* As a consequence, we loose all type information and
* no conversions are possible on re-access. You need
* to know the _exact_ type to get back at the object.
*/
struct InPlaceAnyHolder_unrelatedTypes
{
2025-06-07 23:59:57 +02:00
using Base = void;
template<class SUB>
static void*
convert2base (SUB& obj)
{
return static_cast<void*> (&obj);
}
};
/**
2015-04-16 00:17:35 +02:00
* Inline buffer to hold and own an object while concealing the
* concrete type. The object is given either as ctor parameter or
* by direct assignment; it is copy-constructed into the buffer.
* It is necessary to specify the required buffer storage space
* as a template parameter. InPlaceAnyHolder may be created empty
* or cleared afterwards, and this #empty() state may be detected
* at runtime. In a similar vein, when the stored object has a
* `bool` validity check, this can be accessed though #isValid().
2025-06-07 23:59:57 +02:00
* Moreover `not empty() and isValid()` may be tested as by `bool`
* conversion of the Holder object. The whole compound
* is copyable if and only if the contained object is copyable.
*
* @note assertion failure when trying to place an instance not
* fitting into given size.
* @note \em not threadsafe!
2015-04-16 00:17:35 +02:00
* @todo add support for moving of rvalue refs
*/
template
< size_t siz ///< maximum storage required for the targets to be held inline
, class AccessPolicy = InPlaceAnyHolder_unrelatedTypes
///< how to access the contents via a common interface?
>
class InPlaceAnyHolder
{
using BaseP = AccessPolicy::Base *;
/** Inner capsule managing the contained object (interface) */
struct Buffer
2009-07-04 04:35:17 +02:00
{
alignas(size_t) std::byte content_[siz];
void* ptr() { return &content_; }
virtual ~Buffer() {} ///< this is an ABC with VTable
virtual bool isValid() const =0;
virtual bool empty() const =0;
virtual BaseP getBase() const =0;
virtual void clone (void* targetStorage) const =0;
};
/** special case: no stored object */
struct EmptyBuff : Buffer
{
virtual bool isValid() const { return false; }
virtual bool empty() const { return true; }
BaseP
getBase() const
{
throw error::Invalid("accessing empty holder"
, LERR_(BOTTOM_VALUE));
}
virtual void
clone (void* targetStorage) const
{
new(targetStorage) EmptyBuff();
}
2009-07-04 04:35:17 +02:00
};
2015-04-16 00:17:35 +02:00
/** concrete subclass to manage a specific kind of contained object.
* @note invariant: #content_ always contains a valid SUB object */
2009-07-04 04:35:17 +02:00
template<typename SUB>
struct Buff : Buffer
2009-07-04 04:35:17 +02:00
{
2015-04-16 00:17:35 +02:00
static_assert (siz >= sizeof(SUB), "InPlaceAnyHolder: insufficient Buffer size");
SUB&
get() const ///< core operation: target is contained within the inline buffer
{
return * std::launder (reinterpret_cast<SUB*> (unConst(this)->ptr()));
}
2009-07-04 04:35:17 +02:00
~Buff()
{
get().~SUB();
}
explicit
Buff (SUB const& obj)
{
new(Buffer::ptr()) SUB (obj);
2009-07-04 04:35:17 +02:00
}
Buff (Buff const& oBuff)
{
new(Buffer::ptr()) SUB (oBuff.get());
2009-07-04 04:35:17 +02:00
}
2009-07-04 04:35:17 +02:00
Buff&
operator= (Buff const& ref) ///< currently not used
2009-07-04 04:35:17 +02:00
{
if (&ref != this)
get() = ref.get();
return *this;
}
/* == virtual access functions == */
2009-07-04 04:35:17 +02:00
virtual void
2009-07-04 04:35:17 +02:00
clone (void* targetStorage) const
{
new(targetStorage) Buff(get());
}
virtual BaseP
getBase() const
{
return AccessPolicy::convert2base (get());
2009-07-04 04:35:17 +02:00
}
virtual bool
2009-07-04 04:35:17 +02:00
empty() const
{
return false;
}
virtual bool
2009-07-04 04:35:17 +02:00
isValid() const
{
return validitySelfCheck (this->get());
2009-07-04 04:35:17 +02:00
}
};
2009-07-04 04:35:17 +02:00
enum{ BUFFSIZE = sizeof(Buffer) };
/** embedded buffer actually holding the concrete Buff object,
* which in turn holds and manages the target object.
* @note Invariant: always contains a valid Buffer subclass */
2009-07-04 04:35:17 +02:00
char storage_[BUFFSIZE];
2009-07-05 02:26:59 +02:00
protected: /* === internal interface for managing the storage === */
2009-07-04 04:35:17 +02:00
Buffer&
buff()
{
return * std::launder (reinterpret_cast<Buffer*> (&storage_));
2009-07-04 04:35:17 +02:00
}
const Buffer&
buff() const
{
return * std::launder (reinterpret_cast<const Buffer *> (&storage_));
}
2009-07-05 02:26:59 +02:00
void
killBuffer()
{
buff().~Buffer();
}
void
make_emptyBuff()
{
new(&storage_) EmptyBuff();
2009-07-04 04:35:17 +02:00
}
template<class SUB>
void
place_inBuff (SUB const& obj)
{
2009-07-04 04:35:17 +02:00
new(&storage_) Buff<SUB> (obj);
}
void
clone_inBuff (InPlaceAnyHolder const& ref)
{
ref.buff().clone (storage_);
}
2009-07-05 02:26:59 +02:00
BaseP
asBase () const ///< @internal backdoor e.g. for comparisons
{
BaseP asBase = buff().getBase();
ASSERT (asBase);
return asBase;
}
2009-07-05 02:26:59 +02:00
public:
~InPlaceAnyHolder()
{
killBuffer();
}
void
clear ()
{
killBuffer();
make_emptyBuff();
}
InPlaceAnyHolder()
{
make_emptyBuff();
}
template<class SUB>
InPlaceAnyHolder(SUB const& obj)
{
place_inBuff (obj);
}
InPlaceAnyHolder (InPlaceAnyHolder const& ref)
2009-07-04 04:35:17 +02:00
{
clone_inBuff (ref);
2009-07-04 04:35:17 +02:00
}
InPlaceAnyHolder&
operator= (InPlaceAnyHolder const& ref)
{
if (not isSameObject (*this, ref))
{
killBuffer();
try
{
clone_inBuff (ref);
}
catch (...)
{
make_emptyBuff();
throw;
}
}
return *this;
}
template<class SUB>
InPlaceAnyHolder&
operator= (SUB const& newContent)
{
if (empty()
or not isSameAdr (buff().getBase(), &newContent) // caution: BaseP may be void* and SUB might be a pointer
)
{
killBuffer();
try
{
place_inBuff (newContent);
}
catch (...)
{
make_emptyBuff();
throw;
}
}
return *this;
}
/** re-accessing the concrete contained object.
* This requires exact knowledge of the actual type
* of the element currently in storage. OpaqueHolder
* does not provide any "probing" or visitation mechanism.
* @remarks You might consider lib::Variant or some visitor instead.
* @throws lumiera::error::Logic when conversion/access fails
* @throws lumiera::error::Invalid when accessing an empty holder
*/
template<class SUB>
SUB& get() const
{
2025-06-07 23:59:57 +02:00
using Iface = const Buffer *;
using Actual = const Buff<SUB> *;
Iface interface = &buff();
Actual actual = dynamic_cast<Actual> (interface);
if (actual)
return actual->get();
if (this->empty())
throw error::Invalid("accessing empty holder"
,LERR_(BOTTOM_VALUE));
else
throw error::Logic ("Attempt to access OpaqueHolder's contents "
"specifying incompatible target type"
, LERR_(WRONG_TYPE)
);
}
bool
empty() const
{
return buff().empty();
}
bool
isValid() const
{
return buff().isValid();
}
explicit
operator bool() const
{
return isValid();
}
};
/**
2015-04-16 00:17:35 +02:00
* Inline buffer to hold and own an object while concealing the
* concrete type. Access to the contained object is similar to a
* smart-pointer, but the object isn't heap allocated. OpaqueHolder
* may be created empty, which can be checked by a bool test.
* The whole compound is copyable if and only if the contained
* object is copyable.
*
* # using OpaqueHolder
* OpaqueHolder instances are copyable value objects. They are created
* either empty, by copy from an existing OpaqueHolder, or by directly
* specifying the concrete object to embed. This target object will be
* \em copy-constructed into the internal buffer. Additionally, you
* may assign a new value, which causes the old value object to be
* destroyed and a new one to be copy-constructed into the buffer.
* Later on, the embedded value might be accessed
* - using the smart-ptr-like access through the common base interface BA
* - when knowing the exact type to access, the templated #get might be an option
* - the empty state of the container and a `isValid()` on the target may be checked
* - a combination of both is available as a `bool` check on the OpaqueHolder instance.
*
* For using OpaqueHolder, several *assumptions* need to be fulfilled
* - any instance placed into OpaqueHolder is below the specified maximum size
* - the caller cares for thread safety. No concurrent get calls while in mutation!
*
* @tparam BA the nominal Base/Interface class for a family of types
* @tparam siz maximum storage required for the targets to be held inline
*/
template
< class BA
, size_t siz = sizeof(BA)
>
class OpaqueHolder
2016-12-23 04:23:03 +01:00
: public InPlaceAnyHolder<siz, InPlaceAnyHolder_useCommonBase<BA>>
{
2016-12-23 04:23:03 +01:00
typedef InPlaceAnyHolder<siz, InPlaceAnyHolder_useCommonBase<BA>> InPlaceHolder;
public:
OpaqueHolder() : InPlaceHolder() {}
template<class SUB>
OpaqueHolder(SUB const& obj) : InPlaceHolder(obj) {}
template<class SUB>
OpaqueHolder&
operator= (SUB const& newContent)
{
static_cast<InPlaceHolder&>(*this) = newContent;
return *this;
}
// note: using standard copy operations
/* === smart-ptr style access === */
BA&
operator* () const
{
ASSERT (!InPlaceHolder::empty());
return *InPlaceHolder::buff().getBase();
}
BA*
operator-> () const
{
ASSERT (!InPlaceHolder::empty());
return InPlaceHolder::buff().getBase();
}
};
template<class BA, class DEFAULT>
class PlantingHandle;
/**
* Buffer to place and maintain an object instance privately within another object.
* Variation of a similar concept as with OpaqueHolder, but implemented here
* with reduced security and lesser overhead. InPlaceBuffer is just a chunk of
* storage, which can be accessed through a common base class interface and
* allows to place new objects there. It has no way to keep track of the
* actual object living currently in the buffer. Thus, using InPlaceBuffer
* requires the placed class(es) themselves to maintain their lifecycle,
* and especially it is mandatory for the base class to provide a
* virtual dtor. On the other hand, just the (alignment rounded)
* storage for the object(s) placed into the buffer is required.
* @remarks as a complement, PlantingHandle may be used on APIs to offer
* a lightweight way for clients to provide a callback.
* @warning InPlaceBuffer really takes ownership, and even creates a
* default constructed instance of the base class right away.
* Yet the requirement for a virtual dtor is deliberately not
* enforced here, to allow use for types without VTable.
*
* @tparam BA the nominal Base/Interface class for a family of types
* @tparam siz maximum storage required for the targets to be held inline
* @tparam DEFAULT the default instance to place initially
*/
template
< class BA
, size_t siz = sizeof(BA)
, class DEFAULT = BA
>
class InPlaceBuffer
: util::NonCopyable
{
alignas(BA) mutable
std::byte buf_[siz];
BA&
getObj() const
{
return * std::launder (reinterpret_cast<BA*> (&buf_));
}
void
placeDefault()
{
static_assert (siz >= sizeof(DEFAULT), "InPlaceBuffer too small");
new(&buf_) DEFAULT();
}
void
destroy()
{
getObj().~BA();
}
public:
~InPlaceBuffer ()
{
destroy();
}
InPlaceBuffer ()
{
placeDefault();
}
/** immediately move-emplace an embedded subclass type */
template<class SUB>
InPlaceBuffer (SUB&& instance)
{
static_assert (siz >= sizeof(SUB), "InPlaceBuffer too small");
new(&buf_) SUB (std::forward<SUB> (instance));
}
template<typename TY>
struct TypeTag{ };
/** helper to mark the subclass type to create.
* @remarks we can not specify explicit template arguments on ctor calls,
* so the only way is to use a dummy marker argument to pass the type.
* Use as `InPlaceBuffer(embedType<XYZ>, arg1, arg2, arg3)` */
template<typename SUB>
static auto embedType() { return TypeTag<SUB>{}; }
/** immediately emplace an embedded subclass type */
template<class TY, typename...ARGS>
InPlaceBuffer (TypeTag<TY>, ARGS&& ...args)
{
static_assert (siz >= sizeof(TY), "InPlaceBuffer too small");
new(&buf_) TY (std::forward<ARGS> (args)...);
}
/** a "planting handle" can be used to expose an opaque InPlaceBuffer through an API */
using Handle = PlantingHandle<BA, DEFAULT>;
/** Abbreviation for placement new */
template<class TY, typename...ARGS>
TY&
create (ARGS&& ...args)
{
static_assert (siz >= sizeof(TY), "InPlaceBuffer too small");
destroy();
try {
return *new(&buf_) TY {std::forward<ARGS> (args)...};
}
catch (...)
{
placeDefault();
throw;
}
}
/** move-construct an instance of subclass into the opaque buffer */
template<class SUB>
SUB&
emplace (SUB&& implementation)
{
static_assert (siz >= sizeof(SUB), "InPlaceBuffer too small");
destroy();
try {
return *new(&buf_) SUB {std::forward<SUB> (implementation)};
}
catch (...)
{
placeDefault();
throw;
}
}
DEFAULT&
reset()
{
destroy();
placeDefault();
return static_cast<DEFAULT&> (getObj());
}
/* === smart-ptr style access === */
BA&
operator* () const
{
return getObj();
}
BA*
operator-> () const
{
return &getObj();
}
template<class SUB>
SUB*
access ()
{
BA * asBase = &getObj();
SUB* content = util::AccessCasted<SUB*>::access (asBase);
return content;
} // NOTE: might be null.
};
/**
* A handle to allow for safe _»remote implantation«_
* of an unknown subclass into a given opaque InPlaceBuffer,
* without having to disclose the concrete buffer type or size.
* @remarks this copyable value object is especially geared towards use
* as handle in APIs, allowing a not yet known implementation to implant
* an agent or collaboration partner into the likewise undisclosed innards
* of the service exposed.
* @warning the type BA must expose a virtual dtor, since the targeted
* InPlaceBuffer has to take ownership of the implanted object.
* @note the `siz` (buffer size) template parameter from #InPlaceBuffer
* is deliberately not part of the `PlantingHandle<BA,DEFAULT>` type,
* since buffer size can be considered an opaque implementation detail.
* As a consequence, we must capture this size information at construction
* time and store it at runtime #maxSiz_, to protect against buffer overrun.
* @see OpaqueUncheckedBuffer_test
*/
template<class BA, class DEFAULT = BA>
class PlantingHandle
{
void* buffer_;
size_t maxSiz_;
static_assert (std::has_virtual_destructor<BA>(),
"target interface BA must provide virtual dtor, "
"since InPlaceBuffer needs to take ownership.");
template<class SUB>
void __ensure_can_create();
public:
template<size_t maxSiz>
PlantingHandle (InPlaceBuffer<BA, maxSiz, DEFAULT>& targetBuffer)
: buffer_(&targetBuffer)
, maxSiz_(maxSiz)
{ }
// default copy acceptable...
template<class SUB>
bool
canCreate() const
{
static_assert(std::is_base_of<BA,SUB>(), "concrete object implanted into the opaque "
"buffer must implement the defined interface");
return sizeof(SUB) <= maxSiz_;
}
/** move-construct an instance of a subclass into the opaque buffer */
template<class SUB>
SUB&
emplace (SUB&& implementation)
{
__ensure_can_create<SUB>();
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.template emplace (std::forward<SUB> (implementation));
}
/** Abbreviation for placement new of a subclass SUB into the opaque buffer*/
template<class SUB, typename...ARGS>
SUB&
create (ARGS&& ...args)
{
__ensure_can_create<SUB>();
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.template create<SUB> (std::forward<ARGS> (args)...);
}
BA*
get() const
{
ENSURE (buffer_);
BA& bufferContent = **static_cast<InPlaceBuffer<BA>*> (buffer_);
return &bufferContent;
}
};
/** @internal Helper to ensure the opaque buffer provides sufficient storage
* @tparam SUB actual subclass type to be implanted into the opaque buffer
*/
template<class BA, class B0>
template<class SUB>
inline void
PlantingHandle<BA,B0>::__ensure_can_create()
{
if (not this->canCreate<SUB>())
throw error::Fatal("Unable to implant implementation object of size "
"exceeding the pre-established storage buffer capacity. "
+boost::lexical_cast<std::string>(sizeof(SUB)) + " > "
+boost::lexical_cast<std::string>(maxSiz_)
,error::LUMIERA_ERROR_CAPACITY);
}
} // namespace lib
#endif