实现多类型算术运算符时如何解决“模板参数推导/替换失败”

时间:2019-12-11 05:53:37

标签: c++ c++17

我似乎找不到实现它的正确方法,这似乎是最接近正确方法的方法,但是我遇到了模板参数推导错误。谁能指出我哪里出问题了?

我正在尝试向std::variant添加算术功能,而无需首先std::get

#include <iostream>
#include <variant>

template<typename... Types>
class variant : public std::variant<Types...> {
    private:
      template <class Op, typename T, int index = 0>
      decltype(auto) calc(const T& other) const {
        if(sizeof...(Types) == 0 || index >= sizeof...(Types)){
          return;
        }

        using ST = std::variant_alternative_t<index, std::variant<Types...>>;

        if(std::holds_alternative<ST>(
          std::variant<Types...>(*this)
        )){
          if(std::is_same<T, variant<Types...>>::value){
            return Op()(std::get<ST>(*this), std::get<ST>(other));
          }
          else{
            return Op()(std::get<ST>(*this), other);
          }
        }

        return this->calc<Op, index+1>(other);
      }

    public:
      using std::variant<Types...>::variant;

      template <typename T>
      decltype(auto) operator-(const T& other) const {
        return this->calc<std::minus>(other);
      }

      // other operations will be added; std::plus, etc.
};

int main()
{
    variant<int, double> vt1 = 2.3;
    variant<int, double> vt2 = 5;

    std::cout << "first: " << (vt1 - 2) << std::endl; 

    std::cout << "second: " << (vt2 - vt1) << std::endl; 

    return 0;
}

1 个答案:

答案 0 :(得分:1)

您有几个问题:

  1. std::minus不是类型,而是模板。它无法绑定到class Op。您可能要改用std::minus<>
  2. calc<Op, index + 1>()调用calc<Op, index>()时,您将获得无限递归。开头的if条件没有帮助,因为编译器仍然必须生成该调用:在运行时而不是编译时检查此条件。您需要if constexpr
  3. decltype(auto)的类型不一致返回类型。所有未丢弃的分支应返回相同的类型。
  4. 如果Op()(std::get<ST>(*this), std::get<ST>(other))*this持有不同的类型(并且在您的示例中,他们持有不同的类型),
  5. other将抛出。

您可以简单地定义自由函数并在实现中使用std::visit来代替修复所有这些问题和重新发明std::visit

namespace impl {
    template<class T>
    auto get_value(const T& t) {
        return t;
    }

    template<class... Ts>
    auto get_value(const std::variant<Ts...>& var) {
        using T = std::common_type_t<Ts...>;
        return std::visit([](T value) { return value; }, var);
    }

    template<class Op, class T, class U>
    auto var_op(Op op, const T& t, const U& u) {
        return op(get_value(t), get_value(u));
    }
}

template<class... Ts, class U>
auto operator-(const std::variant<Ts...>& var, const U& u) {
    return impl::var_op(std::minus<>{}, var, u);
}

template<class U, class... Ts>
auto operator-(const U& u, const std::variant<Ts...>& var) {
    return impl::var_op(std::minus<>{}, u, var);
}

template<class... Ts, class... Us>
auto operator-(const std::variant<Ts...>& var1, 
               const std::variant<Us...>& var2) {
    return impl::var_op(std::minus<>{}, var1, var2);
}

如果要将这些功能限制为从my_variant派生的自己的类std::variant,则需要通过添加get_value()来修复static_cast,因为std::visit使用了一些不专门用于std::variant_size的帮助程序类(例如my_variant):

template<class... Ts>
class my_variant : public std::variant<Ts...> {
public:
    using std::variant<Ts...>::variant;
};

...

namespace impl {
    template<class... Ts>
    auto get_value(const my_variant<Ts...>& var) {
        using T = std::common_type_t<Ts...>;
        return std::visit([](T value) { return value; }, 
            static_cast<const std::variant<Ts...>&>(var));
    }
}