toownky
toownky

Reputation: 73

Order independant variadic template arguments

I want to create a system of "Mapper", i.e objects that takes an input (template) value and return a output (template) value. A mapper can have additional features / traits.

Sample code :

// a trait
template <typename I, typename O>
class Writable
{
public:
  virtual ~Writable() = default;
  virtual O & operator()(I i) = 0;
};

// another trait
template <typename I, typename O>
class Bounded
{
public:
  virtual ~Bounded() = default;
  virtual O min() const = 0;
  virtual O max() const = 0;
};

// base class
template <typename I, typename O, template<typename, typename> class ... Traits>
class IMap
  : public Traits<I, O>...
{
public:
  virtual ~IMap() = default;
  virtual O operator()(I i) const = 0;
};

// a test mapper
class Test1
  : public IMap<int, double, Writable, Bounded>
{
public:
  double operator()(int i) const override { return _d; }
  double & operator()(int i) override { return _d; }
  double min() const override { return 0; }
  double max() const override { return 1; }
private:
  double _d;
};

// another test mapper
class Test2
  : public IMap<int, double, Bounded, Writable>
{
public:
  double operator()(int i) const override { return _d; }
  double & operator()(int i) override { return _d; }
  double min() const override { return 0; }
  double max() const override { return 1; }
private:
  double _d;
};

// a class to use mappers
template <typename I, typename O>
class MapUse
{
public:
  // I would like this function to accept an IMap containing "at least" the Bounded trait
  // Ideally this function signature should be like
  void method_requiring_writable(IMap<I, O, Bounded> & map)
  {
    map(123)
    map(123) = 0;
  }
  // I would like this function to accept an IMap containing "at least" the Bounded and Writable traits
  // (no matter the actual order of those traits in variadic template parameter )
  // Ideally this function signature should be like
  void method_requiring_bounded_and_writable(IMap<I, O, Bounded, Writable> & map)
  {
    map(123);
    map(123) = 0;
    map.min();
  }
};

void test()
{
  Test1 t1;
  Test2 t2;
  Test2 * p = dynamic_cast<Test2*>(&t1); // << always return nullptr, of course !

  MapUse<int, double> mu;
  mu.method_requiring_writable(t1); // << does not compile, not the right type
  mu.method_requiring_bounded_and_writable(t1); // << does not compile, not the right type

  return;
}

Error at line : mu.method_requiring_writable(t1) :

error C2664: 'void MapUse<int,double>::method_requiring_writable(IMap<I,O,Bounded> &)': cannot convert argument 1 from 'Test1' to 'IMap<I,O,Bounded> &'
>          with
>          [
>              I=int,
>              O=double
>          ]

Error at line : mu.method_requiring_bounded_and_writable(t1) :

error C2664: 'void MapUse<int,double>::method_requiring_bounded_and_writable(IMap<int,double,Bounded,Writable> &)': cannot convert argument 1 from 'Test1' to 'IMap<int,double,Bounded,Writable> &'

I perfectly understand why this does not work, but I would like a "trick" to make it work !

First problem : Order of traits in variadic template parameter does count. It should easily be solved by forcing the developer to respect traits order, but it's not ideal

Second problem : Be able to create methods that requires a mapped with "at least" a specific set of traits. No clue about this one.

First approach was not variadic, but a - very - complex hierarchy with lots of virtual heritage --> ugly as hell, it's a real pain to add a new trait

With the code above, I thought about adding some kind of cast operators in IMap but failed to do so.

Any C++ / variadic / magic stuff specialist to help on this ?

Upvotes: 2

Views: 79

Answers (1)

AVH
AVH

Reputation: 11516

You can use something like this:

#include <type_traits>    

template <template <class, class> class... Traits,
  std::enable_if_t<
     std::is_base_of<Bounded<I, O>, IMap<I, O, Traits...>>::value
       and std::is_base_of<Writable<I, O>, IMap<I, O, Traits...>>::value
   , int> = 0>
void method_requiring_bounded_and_writable(IMap<I, O, Traits...> & map) {
  // Do stuff
}

Upvotes: 1

Related Questions