Reputation: 42868
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;
}
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&&) {}
^
Upvotes: 2
Views: 788
Reputation:
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
.
Upvotes: 2
Reputation: 303780
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
.
Upvotes: 1
Reputation: 3042
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;
}
Upvotes: 0