【问题标题】:Clang fails to compile parameter pack expansion using template metaprogrammingClang 无法使用模板元编程编译参数包扩展
【发布时间】:2016-07-05 19:00:36
【问题描述】:

我有几个范围boost::variant。在这种情况下,range 只是一个std::pair<It, It>,其中It 是一个迭代器。我用它来存储满足某些属性的迭代器范围。

由于我不知道迭代器的类型,我使用了一点模板元编程来获取std::pairfirst_type,因为我需要第二个包含单个迭代器的boost::variant(对应于一些active 该类型的元素)。

以下代码已简化以帮助解决该问题,但请考虑我的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>;
};

上面的程序用 gcc 可以正确编译,但是用 clang 编译失败。我得到的错误如下:

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_;
       ^

所以,clang 似乎试图获取boost::detail::variant::void_first_type,但不知何故 gcc 识别它并忽略它。如果我使用 &lt;tuple&gt; 标头获取第一个元素的类型,则会发生类似的情况:

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

此更改后的错误不同,但又与 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...>;

我正在使用 boost 1.57.0、gcc 4.8.3 和 clang 3.6.0,始终使用带有 -Wall -Werror -Wextra 标志的 -std=c++11。不能使用其中任何一个的其他版本:-(

任何帮助将不胜感激。如果我的用法不正确,我什至不知道这是否是 clang 或 boost 中的错误,甚至是 gcc 中的错误。提前感谢您的帮助。

【问题讨论】:

  • 你说 g++ 编译你的代码很奇怪。我在 g++ 和 clang 上都有相同的编译错误。
  • 你使用的是同一个版本的gcc吗?我的是 4.8.3。
  • 4.8.2 和 5.3.0 - 两者都编译错误。
  • 嗯,不完全一样。也许有一些相关的差异。 boost的版本呢?我的是 1.57.0。只是出于好奇:您使用 GCC 4.8.2 得到的编译错误究竟是什么?

标签: c++ clang metaprogramming template-meta-programming boost-variant


【解决方案1】:

这不起作用的原因是 boost::variant 没有按照您认为的方式实现。

boost::variant 就像所有的 boost 都兼容 C++03,在可变参数模板出现之前。

因此,boost::variant 必须通过强制使用最大数量的变体并仅使用 C++03 模板功能来解决该语言功能的缺失问题。

他们这样做的方式是,模板有 20 个模板参数,它们都有一个默认值 boost::variant::detail::void_

您的可变参数捕获正在捕获那些额外的参数,就像您尝试将所有参数捕获到std::vector 时一样,即使您没有明确指定,您也会得到您的类型、分配器等分配器。

我能想到的临时解决方法是,

1) 不要使用boost::variant,使用基于可变参数模板的 C++11 变体。有很多实现。

2) 使用 boost 变体,但也创建一个类型特征,允许您从类型列表中恢复原始参数包。您必须确保每次实例化它时,您也会在类型特征中创建一个条目,但您可以使用宏来确保发生这种情况。

3) 可能有一种方法可以让boost::variant 使用基于可变参数模板的实现?但我不确定这一点,我必须查看文档。如果有,则意味着您可以使用一些预处理器定义来强制执行此操作。

编辑:宏实际上是这样的: http://www.boost.org/doc/libs/1_60_0/doc/html/BOOST_VARIANT_DO_NOT_USE_VARIADIC_TEMPLATES.html

所以在最新版本的 boost 中,您必须明确要求不要使用可变参数实现,除非您大概使用 C++03?

您可能希望明确检查您的某个标题中的某些内容是否出于某种原因定义了此内容。

【讨论】:

  • 我认为在 Boost 1.58 中发生了变化
  • 另外,对不构成好的范围...最好使用begin()end() 方法来定义您自己的类模板。
  • 谢谢克里斯。我正在使用 C++11 和 boost 1.57.0,并且没有任何头文件或编译器标志定义该宏。
  • @Janoma:我认为基本上你应该这样看,这是 boost 变体的一个实现细节,如果 boost 确定你的编译器没有足够的 C++11 一致性,它将将 void_ 类型放入您的变体参数列表中。我建议按照我的建议进行选项 (1) 或 (2)。
【解决方案2】:

我们同意 void_boost::variant 的预可变模板解决方法的一部分(每个实例化都是 boost::variant&lt;MandatoryType, ⟪boost::detail::variant::void_ ⨉ ????_???? ⟫&gt;)。

现在,问题是使用metashell 我发现至少有一个版本的boost::variant 使用此解决方法。

环顾四周,我发现有一个 bug recently fixed 关于 boost 库如何无法正确识别 clang 的可变参数模板功能。

回答您的问题:gcc 编译是因为 boost 库识别可变参数模板的可用性,而缺少 clang 的。这会导致 void_ 无法在您的元编程缠结中实例化,因为该 struct 已声明但未定义。

【讨论】:

    【解决方案3】:

    虽然Chris'Felipe's 的贡献部分回答了我的问题(谢谢大家!),但这里有一个更新,它实际上与我提到的 Boost 和 clang 版本一起编译。

    首先,更新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...>;
    };
    

    然后,专门化 ObtainFirstType 结构,使其返回 std::pair&lt;T, T&gt; 的迭代器类型(请记住,在我的用例中,T 是一个迭代器)。

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

    现在,这将编译并工作,但有一个警告。带有 clang 的变体的元素数量将始终为 20,因此任何依赖于此的算法都可能改变其行为。我们可以这样计算它们:

    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);
    };
    

    在我的示例中,我创建了一个包含 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;
    };
    

    GCC 的输出是

    RangeVariant size    : 3
    IteratorVariant size : 3
    

    而 CLANG 的输出如下:

    RangeVariant size    : 20
    IteratorVariant size : 20
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多