Рекурсивное определение и посещение `boost:: variant` с использованием `std:: vector`, содержащего неполный тип — libstdc против libc

#c #boost #c 17 #libc #boost-variant

#c #boost #c 17 #libc #boost-variant

Вопрос:

Я пытаюсь определить и посетить «рекурсивный» boost::variant , используя неполный класс-оболочку и std::vector в качестве моих методов косвенного обращения. Моя реализация работает с libstdc , но не с libc .


Именно так я определяю свой вариант:

 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(Tsamp;amp;... xs) : _v(std::forward<Ts>(xs)...) { }
};
  

Я использую std::vector для введения косвенности (чтобы динамическое распределение не позволяло my_variant иметь бесконечный размер).

Я вполне уверен, что мне разрешено использовать std::vector<my_variant_wrapper> , где my_variant_wrapper неполный тип, из-за статьи N4510 («Минимальная неполная поддержка типов для стандартных контейнеров»):

  • Документ был одобрен в соответствии со страницей WG21 2015 года.

  • Согласно этой странице, функции всегда поддерживались в libstdc .

  • Это было реализовано в libc 3.6, согласно этой странице .


Затем я посещаю вариант следующим образом:

 struct my_visitor
{
    void operator()(int x) const { }
    void operator()(const my_variant_arrayamp; arr) const
    {
        for(const autoamp; 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);
}
  

Минимально полный пример доступен на coliru.


  • Я использую следующие флаги:

    -std=c 1z -Wall -Wextra -Wpedantic

  • BOOST_VERSION вычисляется 106100 как .

Код:

  • Компилируется и выполняется по назначению на:

    • g (протестированные версии: 6.1 и 7), с libstdc .

    • clang (проверенные версии: 3.8), с libstdc .

    • (в качестве бонуса, он также работает с std::variant внесением соответствующих изменений!)

  • Не удается скомпилировать на:

    • clang (протестированные версии: 3.8, 4), с libc .

Это ошибка, которую я получаю при компиляции на clang с 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(Tsamp;amp;... 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 amp;>' 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 amp;>' requested here
            {__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
                 ^

...
  

Полная ошибка доступна в wandbox.


(Может ли это быть дефектом в реализации libc N4510, о котором необходимо сообщить? Почему код не компилируется с помощью libc ?)

Ошибка, по-видимому, предполагает, что варианту не удается определить, какие элементы следует инициализировать, но я, честно говоря, не мог в этом разобраться. Меня также смущает тот факт, что использование libstdc (с той же версией boost) работает так, как ожидалось.

Ответ №1:

Я видел это в обратном пути:

примечание: при создании экземпляра специализации шаблона функции « my_variant_wrapper::my_variant_wrapper<my_variant_wrapper amp;> запрошено здесь

что является явным признаком того, что ваш шаблон конструктора захватывает конструктор копирования.

Ограничьте его, и ваша проблема исчезнет.


Разница между реализациями обусловлена тем, как vector конструктор копирования копирует элементы. libstdc обрабатывает исходные элементы как const :

 vector(const vectoramp; __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());
}
  

Поскольку begin() end() вызываются и const vectoramp; x , они возвращают постоянные итераторы.

libc обрабатывает исходные элементы как не- const :

 template <class _Tp, class _Allocator>
vector<_Tp, _Allocator>::vector(const vectoramp; __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_ и __end_ являются pointer s, а поскольку const являются мелкими, значение const -ness of __x не имеет значения const .

Оба соответствуют, поскольку CopyInsertable требуется возможность копирования как из const источников, так и из других const источников. Однако ваш шаблон перехватывает копирование только из не- const (потому что он теряет копирование из const case с помощью шаблона / не шаблонного прерывателя), поэтому вы видите проблему только в libc .

Комментарии:

1. Потрясающе, спасибо! Он компилируется с libc при ограничении. Я все еще озадачен тем, почему эта ошибка возникает только с libc , хотя — не могли бы вы уточнить это?

2. @VittorioRomeo Просто потратил еще немного времени на копание — см. Редактирование.

3. Таким образом, вектор libc пытается скопировать-сконструировать векторные элементы, используя неконстантную ссылку lvalue, тогда как libstdc передает постоянную ссылку lvalue. Поскольку libc использует неконстантную ссылку, он выбирает конструктор шаблона вместо конструктора копирования.