简体   繁体   中英

Clang fails to compile parameter pack expansion using template metaprogramming

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.

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.

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.

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 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM