Fill a vector with pointers to partially specialized function members automatically

穿精又带淫゛_ 提交于 2019-12-24 03:52:54

问题


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:

  1. Obtain the number of the user defined partially specialised structures named getOutputImpl.
  2. For each such structure determine the output type of its member named apply.
  3. 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_backs 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!