Reputation: 501
I can't seem to find the correct way to implement it, this seems to be the closest to the right way but I am getting a template argument deduction error. Can anyone point out where I went wrong?
I am trying to add arithmetic capabilities to std::variant
without needing to std::get
first:
#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;
}
Upvotes: 1
Views: 301
Reputation: 26282
You have several issues:
std::minus
is not a type, but a template. It cannot bind to class Op
. You might want to use std::minus<>
instead.calc<Op, index + 1>()
from calc<Op, index>()
you get infinite recursion. The if
condition at the beginning does not help, because a compiler still has to generate that call: this condition is checked at the run-time, not at the compile-time. You need if constexpr
.decltype(auto)
return type. All non-discarded branches should return the same type.Op()(std::get<ST>(*this), std::get<ST>(other))
will throw if *this
and other
hold different types (and in your example they do hold different types).Instead of fixing all these and reinventing std::visit
, you can simply define free functions and use std::visit
in the implementation:
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);
}
If you want to limit these functions to your own class my_variant
derived from std::variant
, you need to fix get_value()
by adding static_cast
, because std::visit
uses some helper classes (like std::variant_size
) that are not specialized for 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));
}
}
Upvotes: 1