问题
I am working on a pipeline-like design pattern. One of my design goals is to enable dynamic linking of pipeline segments by providing pointers to function members of a certain data class.
Each of the data classes has a set of function members (representing the data class output ports) indexed using an integer template argument. These functions deduce the return type dynamically using keyword auto
, but all accept the same integer argument c_Idx
, i.e. template <int N> auto getOutput(int c_Idx) const
. The functionality associated with each function getOutput
is defined (by the user) in a set of partially specialised structures getOutputImpl
. Thus, each data class can have from 1 up to some fixed number K
of output data ports.
In order to allow for dynamic linking between the pipeline segments in a generic manner they can be stored in a container of the type std::vector<boost::any>
. However, I need to be able to fill this vector with pointers to the function member templates automatically.
An example of a manual implementation is shown below
template<class TLeafType>
class AlgorithmOutput
{
protected:
std::vector<boost::any> OutputPorts;
public:
AlgorithmOutput()
{
//////////////////////////////////////////
// This procedure needs to be automated //
//////////////////////////////////////////
std::function<std::unique_ptr<double>(int)> pOutFun1 = std::bind(
std::mem_fn(
true ? &AlgorithmOutput<TLeafType>::getOutput<0> : nullptr
),
this,
std::placeholders::_1
);
OutputPorts.push_back(pOutFun1);
std::function<std::unique_ptr<int>(int)> pOutFun2 = std::bind(
std::mem_fn(
true ? &AlgorithmOutput<TLeafType>::getOutput<1> : nullptr
),
this,
std::placeholders::_1
);
OutputPorts.push_back(pOutFun2);
}
virtual ~AlgorithmOutput() {}
protected:
TLeafType* asLeaf(void)
{
return static_cast<TLeafType*>(this);
}
TLeafType const* asLeaf(void) const
{
return static_cast<TLeafType const*>(this);
}
public:
template <int N>
auto getOutput(int c_Idx) const
{
return asLeaf() -> getOutput<N>(c_Idx);
}
boost::any getOutputPort(int PortIdx)
{
return OutputPorts[PortIdx];
}
};
class PipeOutputClass: public AlgorithmOutput<PipeOutputClass>
{
public:
template <int N>
auto getOutput(int c_Idx) const
{
return getOutputImpl<N>::apply(this, c_Idx);
}
template<int N, typename S> friend struct getOutputImpl;
template<int N, typename = void>
struct getOutputImpl
{
static auto apply(
PipeOutputClass const* p_Self,
int c_Idx
)
{ throw std::runtime_error("Wrong template argument."); }
};
template <typename S>
struct getOutputImpl<0, S>
{
static std::unique_ptr<double> apply(
PipeOutputClass const* p_Self,
int c_Idx
)
{
std::unique_ptr<double> mydouble(new double(10));
return mydouble;
}
};
template <typename S>
struct getOutputImpl<1, S>
{
static std::unique_ptr<int > apply(
PipeOutputClass const* p_Self,
int c_Idx
)
{
std::unique_ptr<int > myint(new int(3));
return myint;
}
};
};
The problem with the example above is that I define the member function pointers pOutFunX
manually, whereas I would like to automate this procedure.
Please note that I am not considering design solutions which differ significantly from the design specified above.
Here I present some thoughts on some of the possible approaches for solving this problem. I developed a plan for a solution that I am currently considering, which may be of use if you attempt to answer this question:
- Obtain the number of the user defined partially specialised structures named
getOutputImpl
. - For each such structure determine the output type of its member named
apply
. - Set up a (recursive) meta-template procedure that creates pointers to functions with the relevant signature and adds them to the
OutputPort
vector.
I would assume that the steps 1-3 above will all have to be done at compile time. I am not concerned about the aesthetics of the solution, if it does not require any intervention from the user designing the data output classes. However, I would prefer not to use custom compiler macros.
This post shows how one can infer a member function signature, which may be useful.
回答1:
using AO=AlgorithmOutput;
template<size_t N>
using R=decltype(std::declval<AO*>()->getOutput<N>(0));
template<size_t... Is>
std::vector< boost::any > make_vec( std::index_sequence<Is...> ){
return {
boost::any(
std::function< R<Is>(int) >{
[this](int x){return this->getOutput<N>(x);}
}
)...
};
}
Uses a C++14 type, but one easy to write. Pass it std::make_index_sequence<Count>{}
and it will return a vector populated with any wrapping functions wrapping lambdas which call your apply
.
Your design looks like a mess of static dispatch, dynamic dispatch, type erasure and inefficient pointer semantics, but I assume (charitbly) this is just a simplification of a much more complex problem, and the design is warrented.
However, your runtime check that a static argument is incorrect should not stand. Do not check for compile time errors at runtime.
static auto apply( PipeOutputClass const* p_Self, int c_Idx ) { throw std::runtime_error("Wrong template argument."); }
this seems pointless: make it fail to compile, not compile and throw.
回答2:
We know that for each template argument for which getOutput
is not defined, its return type is void
. So we can determine K
as follows:
template <int K>
constexpr std::enable_if_t<std::is_void<decltype(getOutput<K>(0))>{}, int> getK() {
return K-1;
}
template <int K>
constexpr std::enable_if_t<!std::is_void<decltype(getOutput<K>(0))>{}, int> getK() {
return getK<K+1>();
}
Also, you can "automate" your push_back
s as follows:
AlgorithmOutput() : AlgorithmOutput(std::make_index_sequence<getK<0>()>()) {}
private:
template <std::size_t... indices>
AlgorithmOutput( std::integer_sequence<indices...> )
{
(void)std::initializer_list<int> {
(OutputPorts.push_back([this] (int i) {return getOutput<indices>(i);}, 0)...
};
}
(All code untested, merely sketches!)
来源:https://stackoverflow.com/questions/29733250/fill-a-vector-with-pointers-to-partially-specialized-function-members-automatica