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

Clang fails to compile parameter pack expansion using template metaprogramming

本文关键字:包扩展 编译参数 扩展 编程 Clang      更新时间:2023-10-16

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

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

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

所以,似乎叮当试图获得boost::detail::variant::void_first_type,但不知何故,gcc 识别并忽略了它。如果我使用 <tuple> 标头获取第一个元素的类型,也会发生类似的事情:

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 中的错误。提前感谢您的帮助。

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

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

环顾四周,我发现最近修复了一个关于 boost libs 如何无法正确识别 clang 的可变参数模板功能的错误。

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

这不起作用的原因是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上?

您可能希望显式检查某个标头中的某些内容是否出于某种原因定义了它。

虽然Chris和Felipe的贡献都部分回答了我的问题(谢谢大家!),但这里有一个更新,实际上是用我提到的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<T, T>的迭代器类型(请记住,在我的用例中,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