3

I have a boost::variant of several ranges. In this context, a range is just a std::pair<It, It>, where It is an iterator. I use this to store ranges of iterators satisfying certain properties.

Since I don't know the iterator types, I use a little template meta-programming to obtain the first_type of the std::pair, since I need a second boost::variant containing a single iterator (corresponding to some active element of that type).

The following code is simplified to help with the question, but consider that I have an unknown number of ranges in my RangeVariant (which means I can't create it manually, as I can do for this particular case).

#include <utility>
#include <vector>

#include <boost/variant.hpp>

template <class A, template <typename...> class B>
struct FirstTypeVariantImpl;

template <template <typename...> class A, typename... Pair, template <typename...> class B>
struct FirstTypeVariantImpl<A<Pair...>, B> /*! specialization */
{
    using type = B<typename Pair::first_type...>;
};

template <class A, template <typename...> class B>
using FirstTypeVariant = typename FirstTypeVariantImpl<A, B>::type;

int main()
{
    using Container = std::vector<int>;
    using Range = std::pair<Container::iterator, Container::iterator>;
    using RangeVariant = boost::variant<Range>;
    using IteratorVariant = FirstTypeVariant<RangeVariant, boost::variant>;
};

The above program compiles correctly with gcc, but fails with clang. The error I get is the following:

program.cpp:12:29: error: incomplete type 'boost::detail::variant::void_' named in nested name specifier
using type = B<typename Pair::first_type...>;
                        ^~~~~~
program.cpp:16:1: note: in instantiation of template class 'FirstTypeVariantImpl<boost::variant<std::pair<__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > >, boost::detail::variant::void_, ..., boost::detail::variant::void_>, variant>' requested here
using FirstTypeVariant = typename FirstTypeVariantImpl<A, B>::type;
^
program.cpp:23:29: note: in instantiation of template type alias 'FirstTypeVariant' requested here
using IteratorVariant = FirstTypeVariant<RangeVariant, boost::variant>;
                        ^
../../../include/boost/variant/variant_fwd.hpp:193:8: note: forward declaration of 'boost::detail::variant::void_'
struct void_;
       ^

So, it seems clang is attempting to obtain the first_type of boost::detail::variant::void_, but somehow gcc recognizes it and ignores it. Something similar happens if I obtain the type for the first element using the <tuple> header:

using type = B<typename std::tuple_element<0, Pair>::type...>;

The error after this change is different, but again related to clang trying to apply the operation to boost::detail::variant::void_:

program.cpp:13:34: error: implicit instantiation of undefined template 'std::tuple_element<0, boost::detail::variant::void_>'
using type = B<typename std::tuple_element<0, Pair>::type...>;

I'm using boost 1.57.0, gcc 4.8.3 and clang 3.6.0, always using -std=c++11 with -Wall -Werror -Wextra flags. Using other versions of either of these is not an option :-(

Any help would be appreciated. I don't even know whether this is a bug in clang or boost, or even in gcc, if my usage is incorrect. Thanks in advance for your help.

Janoma
  • 514
  • 4
  • 15
  • It is weird you are saying g++ compiles your code. I have the same compilation errors on both g++ and clang. – SergeyA Mar 18 '16 at 21:26
  • Are you using the same version of gcc? Mine is 4.8.3. – Janoma Mar 18 '16 at 22:08
  • 4.8.2 an 5.3.0 - compile error on both. – SergeyA Mar 18 '16 at 23:45
  • Well, not exactly the same. Perhaps there is some relevant difference. What about the version of boost? Mine is 1.57.0. Just out of curiosity: what exactly is the compilation error that you get with GCC 4.8.2? – Janoma Mar 19 '16 at 00:01

3 Answers3

3

We agree on that void_ is part of boost::variant's pre-variadic template workaround (every instantiation is boost::variant<MandatoryType, ⟪boost::detail::variant::void_ ⨉ _ ⟫>).

Now, the thing is that using metashell I found out there exists at least one version of boost::variant that does not use this workaround.

Looking around, I found that there was a bug recently fixed about how boost libs do not recognize clang's variadic template capability correctly.

To answer your question: gcc compiles because boost libs recognize variadic template availability, while missing clang's. This results in void_ failing to be instantiate in your meta-programming tangle as this struct has been declared, but not defined.

Felipe Lema
  • 2,700
  • 12
  • 19
2

The reason this doesn't work is that boost::variant isn't implemented the way you think it is.

boost::variant like all of boost is compatible with C++03, before the time when there were variadic templates.

As a result, boost::variant has to work around the lack of that language feature, by imposing a maximum number of variants and using only C++03 template features.

The way they do this is, the template has 20 template arguments, and they all have a default value of boost::variant::detail::void_.

Your variadic capture is catching those extra parameters, just the same way that if you tried to capture all the parameters to std::vector you would get your type, also an allocator, etc., even if you didn't explicitly specify an allocator.

The work arounds I can think of off-hand are,

1) Don't use boost::variant, use a C++11 variant based on variadic templates. There are many implementations floating around.

2) Use boost variant, but also create a type-trait that permits you recover the original parameter pack from a typelist. You would have to make sure that every time you instantiate it, you also create an entry in the type trait, but you can use a macro to make sure that happens.

3) There may be a way to make boost::variant use an implementation based on variadic templates? But I'm not sure of this, I would have to review the docs. If there is then it means there is some preprocessor define you can use to force this.

Edit: The macro is this actually: http://www.boost.org/doc/libs/1_60_0/doc/html/BOOST_VARIANT_DO_NOT_USE_VARIADIC_TEMPLATES.html

So in recent versions of boost, you have to request explicitly not to have the variadic implementation, unless you are on C++03 presumably?

You might want to explicitly check if something in one of your headers is defining this for some reason.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • Changed in Boost 1.58 I think – Barry Mar 18 '16 at 21:32
  • also, pairs do not make good ranges... better to define your own class template with a `begin()` and `end()` method for this. – Richard Hodges Mar 18 '16 at 21:55
  • Thanks Chris. I'm using C++11 and boost 1.57.0, and none of the headers or the compiler flags is defining that macro. – Janoma Mar 18 '16 at 22:14
  • @Janoma: I think basically you should look at it like, this is an implementation detail of boost variant, and if boost decides that your compiler doesn't have sufficient C++11 conformance, it will put `void_` types in your variant parameter list. I would recommend doing options (1) or (2) that I suggested. – Chris Beck Mar 18 '16 at 22:57
-1

Although both Chris' and Felipe's contributions answer my question partially (thanks guys!), here is an update that actually compiles with the Boost and clang versions I mentioned.

First, update the specialization of FirstTypeVariant so that it obtains the type from another structure instead of directly obtaining T::first_type:

template <template <typename...> class A, typename... Pair, template <typename...> class B>
struct FirstTypeVariantImpl<A<Pair...>, B> /*! specialization */
{
    using type = B<typename ObtainFirstType<Pair>::type...>;
};

Then, specialize the ObtainFirstType struct so that it returns the iterator type for std::pair<T, T> (remember that in my use case, T is an iterator).

template <typename T>
struct ObtainFirstType
{
    using type = T;
};

template <typename T>
struct ObtainFirstType<std::pair<T, T>>
{
    using type = T;
};

Now, this will compile and work, but there's a caveat. The number of elements of the variant with clang will always be 20, so any algorithm depending on that might change its behavior. We can count them like this:

template <typename... Ts>
struct VariantSize
{
    static constexpr std::size_t size = 0;
};

template <typename... Ts>
struct VariantSize<boost::variant<Ts...>>
{
    static constexpr std::size_t size = sizeof...(Ts);
};

In my example, I created a variant with 3 elements, which I then counted:

int main()
{
    using ContainerA = std::vector<int>;
    using ContainerB = std::vector<double>;
    using ContainerC = std::vector<bool>;
    using RangeA = std::pair<ContainerA::iterator, ContainerA::iterator>;
    using RangeB = std::pair<ContainerB::iterator, ContainerB::iterator>;
    using RangeC = std::pair<ContainerC::iterator, ContainerC::iterator>;

    using RangeVariant = boost::variant<RangeA, RangeB, RangeC>;
    using IteratorVariant = FirstTypeVariant<RangeVariant, boost::variant>;

    std::cout << "RangeVariant size    : " << std::to_string(VariantSize<RangeVariant>::size) << std::endl;
    std::cout << "IteratorVariant size : " << std::to_string(VariantSize<IteratorVariant>::size) << std::endl;
};

The output with GCC is

RangeVariant size    : 3
IteratorVariant size : 3

while the output with CLANG is the following:

RangeVariant size    : 20
IteratorVariant size : 20
Janoma
  • 514
  • 4
  • 15