Reputation: 33
I would like to write an abstraction of linear superpositions using variadic templates. To do that, I would like to define a base type that exhibits a certain form of operator() like so
template <typename Result, typename... Parameters>
class Superposable {
public:
typedef Result result_t;
void operator()(Result& result, const Parameters&...) const = 0;
};
then inherit from it for the current problem, for instance like so
class MyField : public Superposable<double, double, double> {
public:
void operator()(double& result, const double& p1, const double& p2) const {
result = p1 + p2;
}
};
And I would then like to write an abstract base class that can form linear superpositions and gets the Superposable-derived class as a template parameter to determine the call signature of operator(). I would like to have something like
template<typename S> // where S must be inherited from Superposable
class Superposition {
private:
std::vector< std::shared_ptr<S> > elements;
public:
// This is the problem. How do I do this?
void operator()(S::result_t& result, const S::Parameters&... parameters) const {
for(auto p : elements){
S::result_t r;
p->operator()(r, parameters);
result += r;
}
}
};
Here are my questions:
Thanks for your help!
Upvotes: 3
Views: 3601
Reputation: 275936
First, a solution to your problem directly.
Some metaprogramming boilerplate:
template<class...>struct types{using type=types;};
A hana-style function that extracts the types of an argument, optionally taking a template from which to do the extraction based on:
template<template<class...>class Z, class...Args>
constexpr types<Args...> extract_args( Z<Args...> const& ) { return {}; }
An alias that wraps the above in a decltype
for ease of use:
template<template<class...>class Z, class T>
using extract_args_from = decltype( extract_args<Z>( std::declval<T>() ) );
The primary template of Superposition is left empty, with a default argument that extracts the type arguments of Superposable
:
template<class S, class Args=extract_args_from<Superposable, S>>
class Superposition;
then a specialization that gets the Result
and Parameters
types of the (possibly base class of) S
's Superposable
:
template<class S, class Result, class...Parameters>
class Superposition< S, types<Result, Parameters...>> {
live example. Btw, you missed a virtual
.
Note that I don't approve of your design -- the result should be the return value (naturally), making ()
virtual seems like a bad idea (I'd use CRTP instead, and type-erase if I really need to hide the particular implementation).
You can remove the body of Superposable
and it works as-is:
public:
typedef Result result_t;
void operator()(Result& result, const Parameters&...) const = 0;
which I'd recommend at the least.
The next step is to get rid of inheritance and deducing Parameters
at all:
class MyField {
public:
double operator()(const double& p1, const double& p2) const {
return p1 + p2;
}
};
template<typename S> // where S must be inherited from Superposable
class Superposition {
private:
std::vector< std::shared_ptr<S> > elements;
public:
template<class...Parameters,
class R=std::result_of_t<S&(Parameters const&...)>
>
R operator()(const Parameters&... parameters) const {
R ret = {};
for(auto p : elements){
ret += (*p)(parameters...);
}
return ret;
}
};
which has all the flaws of perfect forwarding, but all the advantages as well.
std::result_of_t<?>
is C++14, but replace it with typename std::result_of<?>::type
in C++11 as a drop-in.
Upvotes: 6
Reputation: 48527
template <typename Result, typename... Parameters>
class Superposable
{
public:
using result_t = Result;
using params_t = std::tuple<Parameters...>;
virtual void operator()(Result& result, const Parameters&...) const = 0;
};
class MyField : public Superposable<double, double, double>
{
public:
void operator()(double& result, const double& p1, const double& p2) const
{
result = p1 + p2;
}
};
template <typename, typename>
class Superposition_impl;
template <typename S, std::size_t... Is>
class Superposition_impl<S, std::index_sequence<Is...>>
{
static_assert(std::is_base_of<Superposable<typename S::result_t, typename std::tuple_element<Is, typename S::params_t>::type...>, S>::value, "!");
public:
std::vector<std::shared_ptr<S>> elements;
void operator()(typename S::result_t& result, const typename std::tuple_element<Is, typename S::params_t>::type&... parameters) const
{
for (auto p : elements)
{
typename S::result_t r;
(*p)(r, parameters...);
result += r;
}
}
};
template <typename S>
using Superposition = Superposition_impl<S, std::make_index_sequence<std::tuple_size<typename S::params_t>::value>>;
Upvotes: 1