I am trying to define and visit a "recursive" boost::variant
using an incomplete wrapper class and std::vector
as my indirection techniques. My implementation works with libstdc++, but not with libc++.
This is the way I am defining my variant:
struct my_variant_wrapper;
using my_variant_array = std::vector<my_variant_wrapper>; // <- indirection here
using my_variant = boost::variant<int, my_variant_array>;
struct my_variant_wrapper
{
my_variant _v;
template <typename... Ts>
my_variant_wrapper(Ts&&... xs) : _v(std::forward<Ts>(xs)...) { }
};
I am using std::vector
to introduce indirection (so that dynamic allocation will prevent my_variant
to have infinite size).
I am quite confident I am allowed to use std::vector<my_variant_wrapper>
, where my_variant_wrapper
is an incomplete type, because of paper N4510 ("Minimal incomplete type support for standard containers"):
The paper was approved, according to WG21's 2015 page.
The features has always been supported in libstdc++, according to this page.
It was implemented in libc++ 3.6, according to this page.
I am then visiting the variant as follows:
struct my_visitor
{
void operator()(int x) const { }
void operator()(const my_variant_array& arr) const
{
for(const auto& x : arr)
boost::apply_visitor(*this, x._v);
}
};
int main()
{
my_variant v0 = my_variant_array{
my_variant{1}, my_variant{2}, my_variant_array{
my_variant{3}, my_variant{4}
}
};
boost::apply_visitor(my_visitor{}, v0);
}
A minimal complete example is available on coliru.
I'm using the following flags:
-std=c++1z -Wall -Wextra -Wpedantic
BOOST_VERSION
evaluates to 106100
.
The code:
Compiles and runs as intended on:
g++ (tested versions: 6.1 and 7), with libstdc++.
clang++ (tested versions: 3.8), with libstdc++.
(as a bonus, it also works with std::variant
by making the appropriate changes!)
Fails to compile on:
This is the error I get while compiling on clang++ with libc++:
In file included from prog.cc:2:
In file included from /usr/local/boost-1.61.0/include/boost/variant.hpp:17:
/usr/local/boost-1.61.0/include/boost/variant/variant.hpp:1537:28: error: no matching member function for call to 'initialize'
initializer::initialize(
~~~~~~~~~~~~~^~~~~~~~~~
/usr/local/boost-1.61.0/include/boost/variant/variant.hpp:1692:9: note: in instantiation of function template specialization 'boost::variant<int, std::__1::vector<my_variant_wrapper, std::__1::allocator<my_variant_wrapper> > >::convert_construct<my_variant_wrapper>' requested here
convert_construct(operand, 1L);
^
prog.cc:15:38: note: in instantiation of function template specialization 'boost::variant<int, std::__1::vector<my_variant_wrapper, std::__1::allocator<my_variant_wrapper> > >::variant<my_variant_wrapper>' requested here
my_variant_wrapper(Ts&&... xs) : _v(std::forward<Ts>(xs)...) { }
^
/usr/local/libcxx-head/include/c++/v1/memory:1783:31: note: in instantiation of function template specialization 'my_variant_wrapper::my_variant_wrapper<my_variant_wrapper &>' requested here
::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
^
/usr/local/libcxx-head/include/c++/v1/memory:1694:18: note: in instantiation of function template specialization 'std::__1::allocator<my_variant_wrapper>::construct<my_variant_wrapper, my_variant_wrapper &>' requested here
{__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
^
...
The full error is available on wandbox.
Why is the code not compiling with libc++? (Could this be a defect in libc++'s N4510 implementation that needs to be reported?)
The error seems to suggest that the variant fails to detect what members should be initialized, but I honestly couldn't make much sense of it. I am also confused by the fact that using libstdc++ (with the same boost version) works as expected.
I saw this in the backtrace:
note: in instantiation of function template specialization '
my_variant_wrapper::my_variant_wrapper<my_variant_wrapper &>
' requested here
which is a clear indication that your constructor template is hijacking the copy constructor.
Constrain it, and your problem disappears.
The difference between implementations is due to the way vector
's copy constructor copies the elements. libstdc++ treats the source elements as const
:
vector(const vector& __x)
: _Base(__x.size(),
_Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator()))
{
this->_M_impl._M_finish =
std::__uninitialized_copy_a(__x.begin(), __x.end(),
this->_M_impl._M_start,
_M_get_Tp_allocator());
}
Because begin()
and end()
are called on const vector& x
, they return constant iterators.
libc++ treats the source elements as non-const
:
template <class _Tp, class _Allocator>
vector<_Tp, _Allocator>::vector(const vector& __x)
: __base(__alloc_traits::select_on_container_copy_construction(__x.__alloc()))
{
#if _LIBCPP_DEBUG_LEVEL >= 2
__get_db()->__insert_c(this);
#endif
size_type __n = __x.size();
if (__n > 0)
{
allocate(__n);
__construct_at_end(__x.__begin_, __x.__end_, __n);
}
}
__begin_
and __end_
are pointer
s, and since const
is shallow, the const
-ness of __x
doesn't make the pointee const
.
Both are conforming, since CopyInsertable
requires copyability from both const
and non-const
sources. However, your template only hijacks copying from non-const
(because it loses the copying from const
case by the template/non-template tiebreaker), so you only see the problem in libc++.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With