簡體   English   中英

Clang無法使用模板元編程來編譯參數包擴展

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

我有幾個范圍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_;
       ^

所以,似乎clang試圖獲取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,總是使用-std=c++11-Wall -Werror -Wextra標志。 使用其中任何一個的其他版本不是一個選項:-(

任何幫助,將不勝感激。 如果我的用法不正確,我甚至不知道這是clang或boost中的錯誤,甚至是gcc中的錯誤。 在此先感謝您的幫助。

我們同意void_boost::variant前模板解決方法的一部分(每個實例化都是boost::variant<MandatoryType, ⟪boost::detail::variant::void_ ⨉ 𝖫𝖨𝖲𝖳_𝖲𝖨𝖹𝖤 ⟫> )。

現在,問題是使用metashell我發現存在至少一個版本的boost::variant ,它使用這種解決方法。

環顧四周,我發現最近有一個錯誤修復了如何提升libs無法正確識別clang的可變參數模板功能。

回答你的問題:gcc編譯,因為boost libs識別可變參數模板的可用性,同時缺少clang's。 這導致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上嗎?

您可能希望明確檢查某個標題中的某些內容是否因某種原因而定義了此內容。

盡管克里斯費利佩的貢獻都部分回答了我的問題(謝謝大家!),這里有一個更新,實際上編譯了我提到的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 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM