Why doesn't this boost::variant::operator= call compile?

785 views Asked by At

Why doesn't v = 42 compile here? It seems the compiler is trying to call the copy assignment operator of Foo, why? How can I get it to compile?

#include <boost/variant.hpp>

struct Foo {
    Foo(Foo&&) { }
};

int main() {
    boost::variant<int, Foo> v;
    v = 42;
}

wandbox

The error message is:

In file included from prog.cc:1:
In file included from /usr/local/boost-1.62.0/include/boost/variant.hpp:17:
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:619:21: error: object of type 'Foo' cannot be assigned because its copy assignment operator is implicitly deleted
        lhs_content = ::boost::detail::variant::move(*static_cast<T* >(rhs_storage_));
                    ^
/usr/local/boost-1.62.0/include/boost/variant/detail/visitation_impl.hpp:112:20: note: in instantiation of function template specialization 'boost::detail::variant::move_storage::internal_visit<Foo>' requested here
    return visitor.internal_visit(
                   ^
/usr/local/boost-1.62.0/include/boost/variant/detail/visitation_impl.hpp:154:13: note: in instantiation of function template specialization 'boost::detail::variant::visitation_impl_invoke_impl<boost::detail::variant::move_storage, void *, Foo>' requested here
    return (visitation_impl_invoke_impl)(
            ^
/usr/local/boost-1.62.0/include/boost/variant/detail/visitation_impl.hpp:240:11: note: in instantiation of function template specialization 'boost::detail::variant::visitation_impl_invoke<boost::detail::variant::move_storage, void *, Foo, boost::variant<int, Foo>::has_fallback_type_>' requested here
        , BOOST_VARIANT_AUX_APPLY_VISITOR_STEP_CASE
          ^
/usr/local/boost-1.62.0/include/boost/preprocessor/repetition/repeat.hpp:29:26: note: expanded from macro 'BOOST_PP_REPEAT'
# define BOOST_PP_REPEAT BOOST_PP_CAT(BOOST_PP_REPEAT_, BOOST_PP_AUTO_REC(BOOST_PP_REPEAT_P, 4))
                         ^
/usr/local/boost-1.62.0/include/boost/preprocessor/cat.hpp:22:32: note: expanded from macro 'BOOST_PP_CAT'
#    define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
                               ^
/usr/local/boost-1.62.0/include/boost/preprocessor/cat.hpp:29:34: note: expanded from macro 'BOOST_PP_CAT_I'
#    define BOOST_PP_CAT_I(a, b) a ## b
                                 ^
<scratch space>:128:1: note: expanded from here
BOOST_PP_REPEAT_1
^
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:2384:33: note: in instantiation of function template specialization 'boost::detail::variant::visitation_impl<mpl_::int_<0>, boost::detail::variant::visitation_impl_step<boost::mpl::l_iter<boost::mpl::l_item<mpl_::long_<2>, int, boost::mpl::l_item<mpl_::long_<1>, Foo, boost::mpl::l_end> > >, boost::mpl::l_iter<boost::mpl::l_end> >, boost::detail::variant::move_storage, void *, boost::variant<int, Foo>::has_fallback_type_>' requested here
        return detail::variant::visitation_impl(
                                ^
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:2398:16: note: in instantiation of function template specialization 'boost::variant<int, Foo>::internal_apply_visitor_impl<boost::detail::variant::move_storage, void *>' requested here
        return internal_apply_visitor_impl(
               ^
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:2125:19: note: in instantiation of function template specialization 'boost::variant<int, Foo>::internal_apply_visitor<boost::detail::variant::move_storage>' requested here
            this->internal_apply_visitor(visitor);
                  ^
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:2171:13: note: in instantiation of member function 'boost::variant<int, Foo>::variant_assign' requested here
            variant_assign( detail::variant::move(temp) );
            ^
/usr/local/boost-1.62.0/include/boost/variant/variant.hpp:2189:9: note: in instantiation of function template specialization 'boost::variant<int, Foo>::move_assign<int>' requested here
        move_assign( detail::variant::move(rhs) );
        ^
prog.cc:9:7: note: in instantiation of function template specialization 'boost::variant<int, Foo>::operator=<int>' requested here
    v = 42;
      ^
prog.cc:4:5: note: copy assignment operator is implicitly deleted because 'Foo' has a user-declared move constructor
    Foo(Foo&&) {}
    ^
3

There are 3 answers

2
Barry On BEST ANSWER

From the docs:

Every bounded type must fulfill the requirements of the MoveAssignable concept.

And MoveAssignable requires move assignment (although this isn't spelled out anywhere in the documentation as far as I can find). Foo isn't move-assignable because the user-provided move constructor implicitly deletes the move-assignment operator. Hence, you don't meet the requirements for this operator.


This seems to be a QoI issue. There's no reason that operator=(int ) should require Foo::operator=(Foo ). We can determine at compile-time which type will be the new engaged type (another one of the requirements), and that is the only one for which we would need to instantiate operator=. If the variant was initially Foo, we would simply want to destroy the original Foo and construct a new int.

0
tinkertime On

I think this line of the compiler warning is telling:

prog.cc:4:5: note: copy assignment operator is implicitly deleted because 'Foo' has a user-declared move constructor

The compiler will only generate implicitly declared constructors if no user-declared constructors of any kind are declared

Full description here on Default Constructors

Can of course just tell it to generate this default assignment, but unclear of your actual code and if that'd be ok. Specifically, it's unclear why you have a custom move constructor.

#include <boost/variant.hpp>

struct Foo {
    Foo(Foo&&) { }
    Foo& operator=(const Foo&) = default;
};

int main() {
    boost::variant<int, Foo> v;
    v = 42;
}
5
AudioBubble On

It seems the compiler is trying to call the copy assignment operator of Foo, why?

That's just confusing wording. A move assignment operator (which you don't have either) would suffice.

As for why a move assignment operator is needed: boost::variant's operator= is implemented in terms of assignment from another boost::variant. From your int, a boost::variant<int, Foo> is constructed. v is then move-assigned that boost::variant<int, Foo>. Which needs to account for the possibility that it's being move-assigned a Foo, even though that cannot happen.

You can see the variant_assign helper method responsible for this in your compiler's error message, and in variant.hpp:

template <typename T>
void move_assign(T&& rhs)
{
    // If direct T-to-T move assignment is not possible...
    detail::variant::direct_mover<T> direct_move(rhs);
    if (this->apply_visitor(direct_move) == false)
    {
        // ...then convert rhs to variant and assign:
        //
        // While potentially inefficient, the following construction of a
        // variant allows T as any type convertible to one of the bounded
        // types without excessive code redundancy.
        //
        variant temp( detail::variant::move(rhs) );
        variant_assign( detail::variant::move(temp) );
    }
}

While there's an optimisation there that allows the temporary variant to be skipped, that's only possible when the variant already contains an int, and regardless, it cannot be determined at compile time, so it wouldn't have prevented the instantiation of variant_assign.