Шаблон Variadic, где мы хотим автоматизировать создание оператора @ там, где он существует для каждой переменной шаблона - PullRequest
0 голосов
/ 17 сентября 2018

Предположим, что у нас есть шаблон, подобный приведенному ниже, но мы бы предпочли, чтобы он был переменным, и мы можем автоматизировать создание различных операторов, которые должны быть реализованы. Как мы подходим к этому?

template <typename T1, typename T2, typename T3, typename T4>
class Foo { 
private:
    boost::variant<T1, T2, T3, T4> var_;
public:
    Foo(const T1& t1) : var_(t1) { }
    Foo(const T2& t2) : var_(t2) { }
    Foo(const T3& t3) : var_(t3) { }
    Foo(const T4& t4) : var_(t4) { }

    Foo operator@(const T1& t1) const { return t1 @ boost::get<T1>(var_);}
    Foo operator@(const T2& t2) const { return t2 @ boost::get<T2>(var_);}
    Foo operator@(const T3& t3) const { return t3 @ boost::get<T3>(var_);}
    Foo operator@(const T4& t4) const { return t4 @ boost::get<T4>(var_);}
    Foo operator@(const Foo& foo) const
    {
        switch (foo.which())
        {
         case 0: return *this @ boost::get<T1>(foo.var_);
         case 1: return *this @ boost::get<T2>(foo.var_);
         case 2: return *this @ boost::get<T3>(foo.var_);
         case 3: return *this @ boost::get<T4>(foo.var_);
         }
     }
};

int main()
{
    T1 t11, t12;
    Foo<T1, T2, T3, T4> foo1(t11), foo2(t12);
    Foo<T1, T2, T3, T4> foo = foo1 @ foo2;
    Foo<T1, T2, T3, T4> foo11 = t11 @ foo2;
    Foo<T1, T2, T3, T4> foo12 = foo1 @ t12;
    // assuming we have implemented operator==, I would expect that
    // foo, f11, f12 were all equal here.

    return 0;
}

Я оставляю некоторые проверки типов и другие вещи для краткости.Надеюсь, суть ясна.

Используя std::enable_if_t, я уверен, мы сможем выяснить, как узнать, когда operator@ должен быть реализован для каждого T_i.Однако, есть ли умный способ использовать шаблоны с переменными числами для создания такого класса и иметь все операторы, которые я хочу, канонически перегружены?Мне не нужно специально это делать, поскольку существует только одна версия такого шаблона, который я бы использовал в 99,9% случаев, но я думаю, что это интересный вопрос.

Ответы [ 3 ]

0 голосов
/ 17 сентября 2018

Если вы можете избавиться от «оптимизированной» версии с помощью прямых T1, T4, вы можете просто использовать boost::apply_visitor (/ std::visit в C ++ 17 с std::variant):

template <typename... Ts>
class Foo { 
private:
    boost::variant<Ts...> var_;
public:
    // ...

    friend decltype(auto) operator@(const Foo& lhs_foo, const Foo& rhs_foo)
    {
        return boost::apply_visitor([](auto&& lhs, auto&& rhs) { return lhs @ rhs; },
                                    lhs_foo.var_,
                                    rhs_foo.var_);
    }
};
0 голосов
/ 17 сентября 2018

Я решу это для + и +=.

template<class D, std::size_t I, class T>
struct plus_helper {
  D& operator+=( T const& rhs ) & {
    using boost::get;
    get<I>(self()) += rhs;
    return self();
  }

  friend T operator+( plus_helper<D,I,T>&& self, T const& rhs ) {
    using boost::get;
    return get<I>(std::move(self.self())) + rhs;
  }

  friend T operator+( plus_helper<D,I,T>const& self, T const& rhs ) {
    using boost::get;
    return get<I>(self.self()) + rhs;
  }

private:
  D const& self() const { return *static_cast<D const*>(this); }
  D & self() { return *static_cast<D*>(this); }
};


template<class D, class Indexes, class...Ts>
struct plus_helpers;

template<class D, std::size_t...Is, class...Ts>
struct plus_helpers<D, std::index_sequence<Is...>, Ts...> : plus_helper<D, Is, Ts>...
{
  template<typename T>
  T& get() {return boost::get<T>(self().var_);}
  template<typename T>
  const T& get() const {return boost::get<T>(self().var_);}

  using own_type = plus_helpers<D, std::index_sequence<Is...>, Ts...>;
  //using plus_helper<D,Is,Ts>::operator+...;
  using plus_helper<D,Is,Ts>::operator+=...;

  D& operator+=( D const& rhs )& {
    using fptr = void(*)( D& lhs, D const& rhs );

    // dispatch table: (or use boost::visit)
    static constexpr fptr table[] = {
      (+[]( D& lhs, D const& rhs ) {
        using boost::get;
        lhs += get<Is>(rhs);
      })...
    };

    table[rhs.which()]( self(), rhs );

    return self();
  }

  friend D operator+(own_type&& lhs, D const& rhs ) {
    lhs += rhs;
    return std::move(lhs.self());
  }

  friend D operator+(own_type const& lhs, D const& rhs ) {
    auto tmp = lhs.self();
    return std::move(tmp)+rhs;
  }

private:
  D& self() { return *static_cast<D*>(this); }
  D const& self() const { return *static_cast<D const*>(this); }
};

это использует один бит - using /*[...]*/::operator+...;. Чтобы сделать это в , вам нужно построить дерево (возможно, линейное дерево) из plus_helper и двоичное using operator+ в каждом бутоне.

Вы, вероятно, будете иметь подобный код для каждого оператора; но не идентичны. + и == - это не одно и то же, потому что вы хотите поддерживать +=, но не ===. ;)

Вы, вероятно, захотите несколько макросов, извергающих большую часть описанного выше шаблона.

В финальном классе мы делаем:

template<class...Ts>
class Foo:
 public plus_helpers<Foo<Ts...>, std::index_sequence_for<Ts...>, Ts...>
{
  boost::variant<Ts...> var_;
public:
  template<std::size_t I>
  friend decltype(auto) get( Foo<Ts...> const& foo ) {
    using boost::get;
    return get<std::tuple_element_t<I, std::tuple<Ts...>>>(foo.var_);
  }
  template<std::size_t I>
  friend decltype(auto) get( Foo<Ts...> & foo ) {
    using boost::get;
    return get<std::tuple_element_t<I, std::tuple<Ts...>>>(foo.var_);
  }
};

Вы можете добавить тип на основе get:

template<class T>
friend decltype(auto) get( Foo<Ts...> & foo ) {
    using boost::get;
    return get<T>(foo.var_);
}
template<class T>
friend decltype(auto) get( Foo<Ts...>const & foo ) {
    using boost::get;
    return get<T>(foo.var_);
}

также.

Живой пример .


Наивное наследование / использование замены:

template<class...Bases>
struct inherit_plus_operations {}; // empty
template<class Lhs, class...Rhs>
struct inherit_plus_operations<Lhs, Rhs...>:
  Lhs,
  inherit_plus_operations<Rhs...>
{
  //using Lhs::operator+;
  //using inherit_plus_operations<Rhs...>::operator+;
  using Lhs::operator+=;
  using inherit_plus_operations<Rhs...>::operator+=;
};
template<class Lhs> // one
struct inherit_plus_operations<Lhs>:
  Lhs
{
  //using Lhs::operator+;
  using Lhs::operator+=;
};

template<class D, std::size_t...Is>, class...Ts>
struct plus_helpers<D, std::index_sequence<Is...>, Ts...>:
  inherit_plus_operations<plus_helper<D, Is, Ts>...>

затем избавьтесь от using /* ... */::operator+/*...*/...; от тела.

0 голосов
/ 17 сентября 2018

Подход к поддержанию как можно меньшего количества шаблонов состоит в том, чтобы разрешить неявную конструкцию Foo экземпляров. Таким образом, вам даже не нужно писать никаких перегрузок операторов для типа, отличного от Foo. В качестве примера рассмотрим следующий шаблон.

template <typename ...Args> class Foo { 
    public:
        // Evil shoot-yourself-in-the-foot-catch-all ctor:
        template <class T> Foo(T&& t) : var(std::forward<T>(t)) {}

        friend bool operator == (const Foo<Args...>& lhs, const Foo<Args...>& rhs)
        {
            return lhs.var == rhs.var;
        }

    private:
        std::variant<Args...> var;
};

Это позволяет любому аргументу, который может использоваться для конструирования элемента данных var, неявно создавать экземпляр Foo, подходящий для operator ==.

Foo<int, double, std::string> f1(42);
Foo<int, double, std::string> f2(43);

assert(f1 == f1);
assert(!(f1 == f2));

assert(42 == f1);
assert(!(f1 == 1.2345));
assert(!(f1 == "hello"));
...