4

Using = does not work.

I have code like this, but it is a "bit" ugly.

#include <iostream>
#include <cassert>
#include <variant>
#include <string>

using namespace std;

namespace detail {
    template<typename... L, typename... R>
    void VariantAssignRec(variant<L...>* lhs, const variant<R...>&rhs, size_t rhs_idx, std::integral_constant<int, -1>) {
    }

    template<typename... L, typename... R, int get_idx>
    void VariantAssignRec(variant<L...>* lhs, const variant<R...>&rhs, size_t rhs_idx, std::integral_constant<int, get_idx> = {}) {
        assert(rhs_idx < std::variant_size_v< variant<R...>>);
        if (get_idx == rhs_idx) {
            cout << "assigning from idx " << get_idx << endl;
            *lhs = std::get<get_idx>(rhs);
            return;
        }
        else {
            std::integral_constant<int, get_idx - 1> prev_get_idx;
            VariantAssignRec(lhs, rhs, rhs_idx, prev_get_idx);
        }
    }
}
template<typename... L, typename... R>
void VariantAssign(variant<L...>* lhs, const variant<R...>&rhs) {
    detail::VariantAssignRec(lhs, rhs, rhs.index(), std::integral_constant<int, std::variant_size_v<variant<R...>>-1>{});
}


int main()
{
   std::variant<int, char, std::string> va = 'a';
   std::variant<std::string, int> vb = string("abc");
   cout << "va index is  " << va.index() << endl; 
   cout << "vb index is  " << vb.index() << endl; 
   VariantAssign(&va, vb);
   cout << "va index now should be 2, and it is  " << va.index() << endl; 
   vb = 47;
   VariantAssign(&va, vb);
   cout << "va index now should be 0, and it is  " << va.index() << endl; 
}

I am using VS so no if constexpr but I am looking for general C++17 solution regardless of VC++ lacking support.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • what is your question? Did you post working code? stack overflow isn't for code reviews. Do you somehow want a "prettier" version? If so, what part do you consider bad? – xaxxon Jun 29 '17 at 05:45
  • If you compare your code to large portions of the `std` code, then it is not that bad. The idea of implementing these things, is to hive off the ugliness into a header file, which you can then forget about and write the beautiful code which uses it. – mksteve Jun 29 '17 at 07:27
  • Do you want to allow `char` to `int` conversion ? – Jarod42 Jun 29 '17 at 08:56
  • @Jarod42 I prefer to only match exact types but if it can not be done like that I am fine with any nicer solution. – NoSenseEtAl Jun 29 '17 at 09:13
  • 2
    `visit([&](auto &&v) { dest = v; }, source);`. If the source has types that are not in dest, you can use `visit([&](auto &&v) { if constexpr(is_assignable_v) dest = v; else throw "unsupported"; }, source)` – Johannes Schaub - litb Jun 29 '17 at 10:58
  • 1
    @JohannesSchaub-litb +1, love that solution. I was writing a more complex version of the same thing and realize that yours is far far superior – Curious Jun 29 '17 at 14:07
  • 1
    @JohannesSchaub-litb why don't you convert that to an answer? It's pretty complete – Curious Jun 29 '17 at 14:08

2 Answers2

7

Just use a visitor:

std::variant<A, B, C> dst = ...;
std::variant<B, C> src = B{};

std::visit([&dst](auto const& src) { dst = src; }, src);

If there is a type in src that isn't assignable to dst, this won't compile - which is probably the desired behavior.

If you end up using this pattern semi-often, you could move the assigner into its own function:

template <class T>
auto assignTo(T& dst) {
    return [&dst](auto const& src) { dst = src; };
}

std::visit(assignTo(dst), src);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    beautiful, but for the record I would prefer to avoid writing manually std::visit at call site so I would move everything to the function taking src and dest params. – NoSenseEtAl Jun 29 '17 at 21:35
1

You may use a visitor:

struct overload_priority_low{};
struct overload_priority_high : overload_priority_low{};

template <typename V>
struct AssignTo
{
private:
    V& v; 

public:
    explicit AssignTo(V& v) : v(v) {}

    template <typename T>
    void operator () (T&& t) const
    {
        assign(std::forward<T>(t), overload_priority_high{});   
    }

private:

    template <typename T>
    auto assign(T&& t, overload_priority_high) const
    -> decltype(this->v = std::forward<T>(t), void())
    {
        v = std::forward<T>(t);
    }

    template <typename T>
    void assign(T&& t, overload_priority_low) const
    {
        throw std::runtime_error("Unsupported type");
    }

};

With usage:

int main() {
    std::variant<int, char> v = 0;
    std::variant<int, char, std::string> v2 = 42;

    std::visit(AssignTo(v), v2);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • can you explain overload resolution? I do not understand how overload_priority_low assign even ever gets called. – NoSenseEtAl Jun 29 '17 at 09:48
  • 1
    The overload with `overload_priority_high` uses SFINAE, so if assignation is invalid (for example for something like `std::variant = std::string{}`), that overload is ignored. and the only valid overload is the one with `overload_priority_low`. When both are acceptable, the one with `overload_priority_high` is a better match – Jarod42 Jun 29 '17 at 09:53
  • doh, because of the body of the function... I always thaught of SFINAE mostly wrt function arguments... – NoSenseEtAl Jun 29 '17 at 09:59