简体   繁体   English

Clang无法使用模板元编程来编译参数包扩展

[英]Clang fails to compile parameter pack expansion using template metaprogramming

I have a boost::variant of several ranges . 我有几个范围boost::variant In this context, a range is just a std::pair<It, It> , where It is an iterator. 在这个上下文中,一个范围只是一个std::pair<It, It>It是一个迭代器。 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). 由于我不知道迭代器类型,我使用一些模板元编程来获取std::pairfirst_type ,因为我需要一个包含单个迭代器的第二个boost::variant (对应于它的一些活动元素)类型)。

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). 下面的代码被简化以帮助解决问题,但考虑到我的RangeVariant有一个未知数量的范围(这意味着我不能手动创建它,因为我可以为这个特殊情况做)。

#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. 上面的程序使用gcc正确编译,但是没有使用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. 所以,似乎clang试图获取boost::detail::variant::void_first_type ,但不知何故gcc识别它并忽略它。 Something similar happens if I obtain the type for the first element using the <tuple> header: 如果我使用<tuple>标头获取第一个元素的类型,会发生类似的事情:

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_ : 此更改后的错误是不同的,但再次与clang尝试将操作应用于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. 我正在使用boost 1.57.0,gcc 4.8.3和clang 3.6.0,总是使用-std=c++11-Wall -Werror -Wextra标志。 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. 如果我的用法不正确,我甚至不知道这是clang或boost中的错误,甚至是gcc中的错误。 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_ ⨉ 𝖫𝖨𝖲𝖳_𝖲𝖨𝖹𝖤 ⟫> ). 我们同意void_boost::variant前模板解决方法的一部分(每个实例化都是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. 现在,问题是使用metashell我发现存在至少一个版本的boost::variant ,它使用这种解决方法。

Looking around, I found that there was a bug recently fixed about how boost libs do not recognize clang's variadic template capability correctly. 环顾四周,我发现最近有一个错误修复了如何提升libs无法正确识别clang的可变参数模板功能。

To answer your question: gcc compiles because boost libs recognize variadic template availability, while missing clang's. 回答你的问题:gcc编译,因为boost libs识别可变参数模板的可用性,同时缺少clang's。 This results in void_ failing to be instantiate in your meta-programming tangle as this struct has been declared, but not defined. 这导致void_无法在元编程纠结中实例化,因为此struct已被声明,但未定义。

The reason this doesn't work is that boost::variant isn't implemented the way you think it is. 这不起作用的原因是boost::variant没有像你想象的那样实现。

boost::variant like all of boost is compatible with C++03, before the time when there were variadic templates. 在存在可变参数模板之前, boost::variant就像所有的boost都与C ++ 03兼容。

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. 因此, boost::variant必须通过强加最大数量的变体并仅使用C ++ 03模板功能来解决缺少该语言功能的问题。

The way they do this is, the template has 20 template arguments, and they all have a default value of boost::variant::detail::void_ . 他们这样做的方式是,模板有20个模板参数,它们都有一个默认值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. 你的可变参数捕获正在捕获那些额外的参数,就像你试图捕获std::vector所有参数一样,你会得到你的类型,也是一个分配器等,即使你没有明确指定一个分配器。

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. 1)不要使用boost::variant ,使用基于可变参数模板的C ++ 11变体。 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. 2)使用boost变体,但也创建一个类型特征,允许您从类型列表中恢复原始参数包。 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? 3)有可能有一种方法可以使boost::variant使用基于可变参数模板的实现吗? 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 编辑:宏实际上是这样的: 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? 所以在最近的boost版本中,你必须明确要求不具有可变参数的实现,除非你大概是在C ++ 03上吗?

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. 尽管克里斯费利佩的贡献都部分回答了我的问题(谢谢大家!),这里有一个更新,实际上编译了我提到的Boost和clang版本。

First, update the specialization of FirstTypeVariant so that it obtains the type from another structure instead of directly obtaining T::first_type : 首先,更新FirstTypeVariant以便从另一个结构获取类型,而不是直接获取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). 然后,专门化ObtainFirstType结构,以便它返回std::pair<T, T>的迭代器类型(请记住,在我的用例中, T是一个迭代器)。

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. 具有clang的变体的元素数量将始终为20,因此任何依赖于该变量的算法都可能会改变其行为。 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: 在我的例子中,我创建了一个包含3个元素的variant ,然后我计算了它:

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 GCC的输出是

 RangeVariant size : 3 IteratorVariant size : 3 

while the output with CLANG is the following: 而CLANG的输出如下:

 RangeVariant size : 20 IteratorVariant size : 20 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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