Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively defining and visiting a `boost::variant` using an `std::vector` containing an incomplete type - libstdc++ vs libc++

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:

    • clang++ (tested versions: 3.8, 4), with libc++.

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.

like image 465
Vittorio Romeo Avatar asked Oct 13 '16 10:10

Vittorio Romeo


1 Answers

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 pointers, 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++.

like image 111
T.C. Avatar answered Nov 14 '22 10:11

T.C.