Clang 无法使用模板元编程编译参数包扩展
Clang fails to compile parameter pack expansion using template metaprogramming
我有几个 范围 的 boost::variant
。在此上下文中,range 只是一个 std::pair<It, It>
,其中 It
是一个迭代器。我用它来存储满足某些属性的迭代器范围。
由于我不知道迭代器类型,我使用了一个小模板 meta-programming 来获取 std::pair
的 first_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 识别并忽略了它。如果我使用 <tuple>
header:
获取第一个元素的类型,则会发生类似的情况
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,始终使用 -std=c++11
和 -Wall -Werror -Wextra
标志。使用其中任何一个的其他版本都不是一个选项:-(
如有任何帮助,我们将不胜感激。如果我的用法不正确,我什至不知道这是否是 clang 或 boost 中的错误,甚至是 gcc 中的错误。预先感谢您的帮助。
这不起作用的原因是 boost::variant
没有按照您认为的方式实现。
boost::variant
就像所有的 boost 都与 C++03 兼容,在有可变参数模板之前。
因此,boost::variant
必须通过强加最大数量的变体和仅使用 C++03 模板功能来解决缺少该语言功能的问题。
他们这样做的方式是,模板有 20 个模板参数,它们都有默认值 boost::variant::detail::void_
。
您的可变参数捕获正在捕获那些额外的参数,就像您尝试将所有参数捕获到 std::vector
一样,您将获得您的类型、分配器等,即使您没有't 显式指定分配器。
我能想到的解决方法 off-hand 是,
1) 不要使用 boost::variant
,使用基于可变参数模板的 C++11 变体。有许多实现浮动。
2) 使用 boost 变体,但也创建一个 type-trait 允许您从类型列表中恢复原始参数包。您必须确保每次实例化它时,您还在类型特征中创建一个条目,但您可以使用宏来确保发生这种情况。
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 上?
您可能想要明确检查您的 headers 中的某些内容是否出于某种原因定义了此内容。
我们同意 void_
是 boost::variant
预可变模板解决方法的一部分(每个实例化都是 boost::variant<MandatoryType, ⟪boost::detail::variant::void_ ⨉ _ ⟫>
)。
现在,问题是使用 metashell 我发现至少存在一个版本的 boost::variant
不 使用此解决方法。
环顾四周,我发现 bug recently fixed 关于 boost 库如何无法正确识别 clang 的可变参数模板功能。
回答你的问题:gcc 编译是因为 boost 库识别可变参数模板可用性,但缺少 clang 的。这导致 void_
无法在您的元编程纠结中实例化,因为此 struct
已声明,但未定义。
虽然 and 贡献部分回答了我的问题(谢谢大家!),但这里有一个更新实际上是用我提到的 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
结构,使其 returns 成为 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
我有几个 范围 的 boost::variant
。在此上下文中,range 只是一个 std::pair<It, It>
,其中 It
是一个迭代器。我用它来存储满足某些属性的迭代器范围。
由于我不知道迭代器类型,我使用了一个小模板 meta-programming 来获取 std::pair
的 first_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 识别并忽略了它。如果我使用 <tuple>
header:
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,始终使用 -std=c++11
和 -Wall -Werror -Wextra
标志。使用其中任何一个的其他版本都不是一个选项:-(
如有任何帮助,我们将不胜感激。如果我的用法不正确,我什至不知道这是否是 clang 或 boost 中的错误,甚至是 gcc 中的错误。预先感谢您的帮助。
这不起作用的原因是 boost::variant
没有按照您认为的方式实现。
boost::variant
就像所有的 boost 都与 C++03 兼容,在有可变参数模板之前。
因此,boost::variant
必须通过强加最大数量的变体和仅使用 C++03 模板功能来解决缺少该语言功能的问题。
他们这样做的方式是,模板有 20 个模板参数,它们都有默认值 boost::variant::detail::void_
。
您的可变参数捕获正在捕获那些额外的参数,就像您尝试将所有参数捕获到 std::vector
一样,您将获得您的类型、分配器等,即使您没有't 显式指定分配器。
我能想到的解决方法 off-hand 是,
1) 不要使用 boost::variant
,使用基于可变参数模板的 C++11 变体。有许多实现浮动。
2) 使用 boost 变体,但也创建一个 type-trait 允许您从类型列表中恢复原始参数包。您必须确保每次实例化它时,您还在类型特征中创建一个条目,但您可以使用宏来确保发生这种情况。
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 上?
您可能想要明确检查您的 headers 中的某些内容是否出于某种原因定义了此内容。
我们同意 void_
是 boost::variant
预可变模板解决方法的一部分(每个实例化都是 boost::variant<MandatoryType, ⟪boost::detail::variant::void_ ⨉ _ ⟫>
)。
现在,问题是使用 metashell 我发现至少存在一个版本的 boost::variant
不 使用此解决方法。
环顾四周,我发现 bug recently fixed 关于 boost 库如何无法正确识别 clang 的可变参数模板功能。
回答你的问题:gcc 编译是因为 boost 库识别可变参数模板可用性,但缺少 clang 的。这导致 void_
无法在您的元编程纠结中实例化,因为此 struct
已声明,但未定义。
虽然
首先,更新 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
结构,使其 returns 成为 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