Я работаю над конвейерным шаблоном дизайна. Одна из моих целей разработки — включить динамическое связывание сегментов конвейера, предоставляя указатели для функций-членов определенного класса данных.
Каждый из классов данных имеет набор функций-членов (представляющих выходные порты класса данных), проиндексированных с использованием целочисленного аргумента шаблона. Эти функции определяют тип возвращаемого значения динамически, используя ключевое слово auto
, но все принимают один и тот же целочисленный аргумент c_Idx
т.е. template <int N> auto getOutput(int c_Idx) const
, Функциональность, связанная с каждой функцией getOutput
определяется (пользователем) в наборе частично специализированных структур getOutputImpl
, Таким образом, каждый класс данных может иметь от 1 до некоторого фиксированного числа K
выходных портов данных.
Для обеспечения динамического связывания между сегментами конвейера в общем виде они могут храниться в контейнере типа std::vector<boost::any>
, Однако мне нужно иметь возможность автоматически заполнять этот вектор указателями на шаблоны членов функции.
Пример ручной реализации показан ниже
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;
}
};
};
Проблема с примером выше в том, что я определяю указатели на функции-члены pOutFunX
вручную, тогда как я хотел бы автоматизировать эту процедуру.
Обратите внимание, что я не рассматриваю дизайнерские решения, которые значительно отличаются от дизайна, указанного выше.
Здесь я представляю некоторые мысли о некоторых возможных подходах к решению этой проблемы. Я разработал план решения, которое я сейчас рассматриваю, и которое может пригодиться, если вы попытаетесь ответить на этот вопрос:
getOutputImpl
,apply
,OutputPort
вектор.Я бы предположил, что шаги 1-3, описанные выше, должны быть выполнены во время компиляции. Меня не волнует эстетика решения, если оно не требует вмешательства пользователя, разрабатывающего классы вывода данных. Однако я бы предпочел не использовать пользовательские макросы компилятора.
Этот пост показывает, как можно вывести сигнатуру функции-члена, что может быть полезно.
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);}
}
)...
};
}
Использует тип C ++ 14, но легко написать. Пропусти это std::make_index_sequence<Count>{}
и он вернет вектор, заполненный любыми функциями-обёртками, которые обертывают ваши apply
,
Ваш дизайн выглядит как беспорядок статической диспетчеризации, динамической диспетчеризации, стирания типов и неэффективной семантики указателей, но я предполагаю (мягко говоря), что это просто упрощение гораздо более сложной проблемы, и дизайн оправдан.
Тем не менее, ваша проверка времени выполнения, что статический аргумент неверен, не должна стоять. Не проверяйте наличие ошибок времени компиляции во время выполнения.
static auto apply( PipeOutputClass const* p_Self, int c_Idx ) { throw std::runtime_error("Wrong template argument."); }
это кажется бессмысленным: заставить его не скомпилировать, а не скомпилировать и выбросить.
Мы знаем, что для каждого аргумента шаблона, для которого getOutput
не определен, его тип возвращаемого значения void
, Таким образом, мы можем определить, K
следующее:
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>();
}
Также вы можете «автоматизировать» свой push_back
следующим образом:
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)...
};
}
(Весь код не проверен, просто наброски!)