Search code examples
c++recursive-datastructuresboost-variantboost-spirit-x3

boost spirit x3 variant Recursive assignment problem


In special scenarios

I have this data structure

namespace x3 = boost::spirit::x3;
struct one;
struct two : x3::variant<x3::forward_ast<one>,int>{
    using base_type::base_type;
    using base_type::operator=;
    two& operator=(const std::string& rhs){
        return *this;
    }
    two& operator=(const std::string&& rhs){
        return *this;
    }
};
struct one :  x3::variant<boost::recursive_wrapper<two>,std::string>{
    using base_type::base_type;
    using base_type::operator=;
};

In this case, the assignment is OK

one  on{"sssss"};
two  wo{22};
wo = 123;
on = std::string("vvvvvvvvvvv");
on = wo;

But when I directly assign a string to wo, it prompts an error:

wo = on;
// wo = one("sadfasdfas"); //You can't do this
wo = std::string("sadfasdfas"); <----【/usr/local/include/boost/variant/variant.hpp:2172:14: Candidate function not viable: no known conversion from 'std::string' to 'boost::variant<boost::spirit::x3::forward_ast<one>, int>' for 1st argument】

I have overloaded the relevant functions, but it seems to have no effect。 Who knows why this is and tell me how to make the correct modifications


Solution

  • The problem is that you overloaded two::operator= for const std::string& and const std::string&&, neither of which is the best match for overload resolution with (non-const) rvalue argument std::string("vvvvvvvvvvv"). Instead, the best match comes from x3::variant::operator=(T&&) (source) which you included with using base_type::operator=:

     template <typename T, class = non_self_t<T>>
     variant& operator=(T&& rhs) BOOST_NOEXCEPT_IF((std::is_nothrow_assignable<variant_type, T&&>::value))
     {
         var = std::forward<T>(rhs);
         return *this;
     }
    

    The line var = std::forward<T>(rhs); results in compilation error you mentioned (var is boost::variant<boost::spirit::x3::forward_ast<one>, int> to which a string cannot be assigned).

    To make your overloads work as intended you can just make your rvalue assignment operator accept non-const rvalue reference to make it higher priority in overload resolution than templated operator= (and to actually make it possible to adequately implement, because you wouldn't be able to move from const std::string&& anyway):

    two& operator=(std::string&& rhs) {
        return *this;
    }
    

    (godbolt)

    Note that you don't need to 'break inheritance cycle' second time in one by boost::recursive_wrapper, it can be removed. Also, // wo = one("sadfasdfas"); can be uncommented and work as intended as well.