paradiseo/eo/src/eoForge.h
nojhan 13d62d0445 feat add cache management to forges
- Disabled by default, because dangerous on statefull operators.
- More doc.
2020-04-20 15:26:51 +02:00

293 lines
8.6 KiB
C++

/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2 of the License.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
© 2020 Thales group
Authors:
Johann Dreo <johann.dreo@thalesgroup.com>
*/
#ifndef _eoForge_H_
#define _eoForge_H_
#include <string>
#include <tuple>
// In case you want to debug arguments captured in tuples:
// template<typename Type, unsigned N, unsigned Last>
// struct tuple_printer {
//
// static void print(std::ostream& out, const Type& value) {
// out << std::get<N>(value) << ", ";
// tuple_printer<Type, N + 1, Last>::print(out, value);
// }
// };
//
// template<typename Type, unsigned N>
// struct tuple_printer<Type, N, N> {
//
// static void print(std::ostream& out, const Type& value) {
// out << std::get<N>(value);
// }
//
// };
//
// template<typename... Types>
// std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
// out << "(";
// tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value);
// out << ")";
// return out;
// }
/**
* @defgroup Foundry
*
* Tools for automatic algorithms assembling, selection and search.
*/
/** Interface for a "Forge": a class that can defer instanciation of EO's operator.
*
* This interface only declares an `instanciate` method,
* in order to be able to make containers of factories (@see eoForgeOperator).
*
* @ingroup Core
* @ingroup Foundry
*/
template<class Itf>
class eoForgeInterface
{
public:
virtual Itf& instanciate(bool no_cache = true) = 0;
virtual ~eoForgeInterface() {}
};
/** This "Forge" can defer the instanciation of an EO's operator.
*
* It allows to decouple the constructor's parameters setup from its actual call.
* You can declare a parametrized operator at a given time,
* then actually instanciate it (with the given interface) at another time.
*
* This allows for creating containers of pre-parametrized operators (@see eoForgeVector).
*
* @code
eoForgeOperator<eoselect<EOT>,eoRankMuSelect<EOT>> forge(mu);
// ^ desired ^ to-be-instanciated ^ operator's
// interface operator parameters
// Actual instanciation:
eoSelect<EOT>& select = forge.instanciate();
* @endcode
*
* @warning You may want to enable instanciation cache to grab some performances.
* The default is set to disable the cache, because its use with operators
* which hold a state will lead to unwanted behaviour.
*
* @ingroup Foundry
*/
template<class Itf, class Op, typename... Args>
class eoForgeOperator : public eoForgeInterface<Itf>
{
public:
eoForgeOperator(Args... args) :
_args(args...),
_instanciated(nullptr)
{ }
/** Instanciate the managed operator class.
*
* That is call its constructor with the set up arguments.
*
* @warning Do not enable cache with operators which hold a state.
*
* @param no_cache If false, will enable caching previous instances.
*/
Itf& instanciate(bool no_cache = true)
{
if(no_cache or not _instanciated) {
if(_instanciated) {
delete _instanciated;
}
_instanciated = constructor(_args);
}
return *_instanciated;
}
virtual ~eoForgeOperator()
{
delete _instanciated;
}
protected:
std::tuple<Args...> _args;
private:
/** Metaprogramming machinery which deals with arguments lists @{ */
template <int... Idx>
struct index {};
template <int N, int... Idx>
struct gen_seq : gen_seq<N - 1, N - 1, Idx...> {};
template <int... Idx>
struct gen_seq<0, Idx...> : index<Idx...> {};
template <typename... Ts, int... Idx>
Op* constructor(std::tuple<Ts...>& args, index<Idx...>)
{
Op* p_op = new Op(std::get<Idx>(args)...);
_instanciated = p_op;
return p_op;
}
template <typename... Ts>
Op* constructor(std::tuple<Ts...>& args)
{
return constructor(args, gen_seq<sizeof...(Ts)>{});
}
/** @} */
protected:
Itf* _instanciated;
};
/** Partial specialization for constructors without any argument.
*/
template<class Itf, class Op>
class eoForgeOperator<Itf,Op> : public eoForgeInterface<Itf>
{
public:
eoForgeOperator() :
_instanciated(nullptr)
{ }
Itf& instanciate( bool no_cache = true )
{
if(no_cache or not _instanciated) {
if(_instanciated) {
delete _instanciated;
}
_instanciated = new Op;
}
return *_instanciated;
}
virtual ~eoForgeOperator()
{
delete _instanciated;
}
protected:
Itf* _instanciated;
};
/** A vector holding an operator with deferred instanciation at a given index.
*
* @note You can actually store several instances of the same class,
* with different parametrization (or not).
*
* @warning You may want to enable instanciation cache to grab some performances.
* The default is set to disable the cache, because its use with operators
* which hold a state will lead to unwanted behaviour.
*
* @code
eoForgeVector<eoSelect<EOT>> factories(false);
// Capture constructor's parameters and defer instanciation.
factories.add<eoRankMuSelect<EOT>>(1);
factories.setup<eoRankMuSelect<EOT>>(0, 5); // Edit
// Actually instanciate.
eoSelect<EOT>& op = factories.instanciate(0);
// Call.
op();
* @endcode
*
* @ingroup Foundry
*/
template<class Itf>
class eoForgeVector : public std::vector<eoForgeInterface<Itf>*>
{
public:
/** Default constructor do not cache instanciations.
*
* @warning
* You most probably want to disable caching for operators that hold a state.
* If you enable the cache, the last used instanciation will be used,
* at its last state.
* For example, continuators should most probably not be cached,
* as they very often hold a state in the form of a counter.
* At the end of a search, the continuator will be in the end state,
* and thus always ask for a stop.
* Reusing an instance in this state will de facto disable further searches.
*
* @param always_reinstanciate If false, will enable cache for the forges in this container.
*/
eoForgeVector( bool always_reinstanciate = true ) :
_no_cache(always_reinstanciate)
{ }
/** Add an operator to the list.
*/
template<class Op, typename... Args>
void add(Args... args)
{
auto pfo = new eoForgeOperator<Itf,Op,Args...>(args...);
this->push_back(pfo);
}
/** Specialization for operators with empty constructors.
*/
template<class Op>
void add()
{
eoForgeInterface<Itf>* pfo = new eoForgeOperator<Itf,Op>;
this->push_back(pfo);
}
/** Change the set up arguments to the constructor.
*
* @warning The operator at `index` should have been added with eoForgeVector::add already..
*/
template<class Op, typename... Args>
void setup(size_t index, Args... args)
{
assert(this->at(index) != nullptr);
delete this->at(index);
auto pfo = new eoForgeOperator<Itf,Op,Args...>(args...);
this->at(index) = pfo;
}
/** Instanciate the operator managed at the given index.
*/
Itf& instanciate(size_t index)
{
return this->at(index)->instanciate(_no_cache);
}
virtual ~eoForgeVector()
{
for(auto p : *this) {
delete p;
}
}
protected:
bool _no_cache;
};
#endif // _eoForge_H_