简体   繁体   中英

C++11 variadic template + inheritance

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:

  1. How do I read out the type information from the Superposable-derived class to define my operator() in Superposition?
  2. Also, is there a recommended way to enforce that Superposition can only be called with a Superposable-derived class as an argument?
  3. An even nicer solution would of course be to write a Superposition class that does not require MyField to be derived from a base class, but directly parses MyField's operator(). Can I do this somehow?

Thanks for your help!

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.

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>>;

DEMO

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM