0

我试图从我的实际项目中删除尽可能多的代码。这是重现此错误所需的最少代码。似乎编译器需要更多的空间,更多的类型被添加到基本类型列表中。

这是为什么?以及 如何解决此限制的任何想法?

该代码从其他类型列表和类型构造一个扁平类型列表,并删除重复项以为列表中注册的每种类型提供唯一的 id。

我需要此代码来处理类型列表中至少 30-50 种类型。代码的目的是为显式列出的类型提供运行时类型反射系统,如下例所示:

#include <tuple>

// Utils
// Type for concatenated tuples
template<typename... input_t> using tuple_cat_t = decltype(std::tuple_cat(std::declval<input_t>()...));

// Helper to check if a type is contained in a tuple already
template <typename T, typename Tuple> struct contains;
template <typename T, typename... Us> struct contains<T, std::tuple<Us...>> : std::disjunction<std::is_same<T, Us>...> {};
template< class T, class U > inline constexpr bool contains_v = contains<T, U>::value;

// Filter Code:
template <class Out, class In> struct filter_duplicates;

// Partial spezialization for the recursive deduction to end (when input tuple is empty)
template <class Out> struct filter_duplicates<Out /*finished filtered output tuple*/, std::tuple<> /*empty input tuple*/>
{
    using filtered_tuple_t = Out;

    // The filter function deduction ends here if the input tuple is empty (every element checked). In this case _In is the found filtered output tuple
    static constexpr Out& filter(Out&& _In) { return _In; }
};

/* Filter template that is used as long the input tuple is not empty. 
It builds the output tuple by recursively checking every element in the 
input tuple and deciding whether is can be added to the output tuple and 
then continuous deduction with the next input element until the 
filter_duplicates definition above matches. */
template <class... OutTypes, class InTest, class... InRest>
struct filter_duplicates<std::tuple<OutTypes...>, std::tuple<InTest, InRest...>>
{
    // contained is true if the InTest element from the input tuple is contained in the output tuple already
    static constexpr bool contained = contains_v<InTest, std::tuple<OutTypes...>>;
    // depending on the condition above either add_t or rem_t is used (which adds InTest to the output tuple or not
    using add_t = filter_duplicates<std::tuple<OutTypes..., InTest>, std::tuple<InRest...>>;
    using rem_t = filter_duplicates<std::tuple<OutTypes...        >, std::tuple<InRest...>>;
    // These types resolve to a tuple<...> with either added or remove InTest type
    using add_tuple_t = typename add_t::filtered_tuple_t;
    using rem_tuple_t = typename rem_t::filtered_tuple_t;
    // This type is the result of the check if InTest is contained, so its either add_tuple_t or rem_tuple_t
    using filtered_tuple_t = std::conditional_t<contained, rem_tuple_t, add_tuple_t>;
    // This type is the result of the check if InTest is contained, so its either the filter_duplicate type containing InTest in the OutTypes or not
    using filter_t = std::conditional_t<contained, rem_t, add_t>;

    // The function takes the unfiltered tuple instance and returns the filtered tuple instance (duplicate tuple entries are filtered)
    static constexpr auto filter(std::tuple<OutTypes..., InTest, InRest...>&& _In)
    {
        return filter_seq<contained>(std::make_index_sequence<sizeof...(OutTypes)>{}, std::make_index_sequence<sizeof...(InRest) + 1 - contained>{}, std::move(_In));
    }

    // The input tuple for the next deduction step is built by getting all tuple elements except "InTest" in the case it is in the output list already
    template<size_t _Skip, size_t... TIndicesOut, size_t... TIndicesIn, class... In>
    static constexpr auto filter_seq(std::index_sequence<TIndicesOut...>, std::index_sequence<TIndicesIn...>, std::tuple<In...>&& _In)
    {
        return filter_t::filter(std::make_tuple(std::move(std::get<TIndicesOut>(_In))..., std::move(std::get<sizeof...(TIndicesOut) + _Skip + TIndicesIn>(_In))...));
    }
};
// Some declarations for easier use
template <class T> using no_duplicates_tuple_t = typename filter_duplicates<std::tuple<>, T>::filtered_tuple_t;
template <class T> using no_duplicates_filter = filter_duplicates<std::tuple<>, T>;
// Function to return a filtered tuple given an unfiltered tuple. It uses the filter type above to construct the new tuple with correct type
template<class... In> constexpr auto make_tuple_no_duplicates(std::tuple<In...>&& _In)
{
    return no_duplicates_filter<std::tuple<In...>>::filter(std::move(_In));
}

// Type info wrapper (In my project it contains functions like construct or copy and much more for the runtime type reflection)
struct IType {};
struct STypeUnknown : IType { using TType = void; };
template<typename T> struct SType : IType { using TType = T; };

// STypeList forward declation
template <typename...> struct STypeList;

// This type unwrappes a given STypeList into a flattened plain tuple, so it can be concatenated with other STypeLists or Types

// In case the given type is just a normal type, it is converted into a tuple with a single element: tuple<T> (so tuple_cat() can be used later)
template<typename T> struct SUnwrapTypeList
{
    using tuple_type = std::tuple<T>;
    tuple_type as_tuple;
    SUnwrapTypeList(T& _Value) : as_tuple{ _Value } {}
};
// In case the Type is a STypeList its filtered and flattened tuple is used
template<typename... TypeDefs> struct SUnwrapTypeList<STypeList<TypeDefs...>>
{
    using TypeList = STypeList<TypeDefs...>;
    using tuple_type = typename TypeList::TTupleDef;
    tuple_type as_tuple;
    SUnwrapTypeList(TypeList& _Value) : as_tuple(_Value.infos) {}
};

// The actual STypeList that can be constructed from other STypeList instances
template<typename... TypeDefs> struct STypeList
{
    // Defines the actual underlying flattened and filtered tuple<...>
    using TTupleDef = no_duplicates_tuple_t<tuple_cat_t<typename SUnwrapTypeList<TypeDefs>::tuple_type...>>;
    // Type count after flattening and filtering
    static constexpr size_t TypeCount() { return std::tuple_size<TTupleDef>::value; }
    // helper to get an index_sequence for the filtered flattened tuple
    static constexpr auto type_seq = std::make_index_sequence<TypeCount()>{};

    // All type infos given via the STypeList constructor, flattened and filtered
    TTupleDef infos;

    // This constructor is used in the example below. It can take arbitrary STypeLists or SType<XXX> lists and flattens and filteres them using the code above
    STypeList(TypeDefs... _Types) noexcept : STypeList(type_seq, std::tuple_cat(SUnwrapTypeList<TypeDefs>(_Types).as_tuple...)) {}
    STypeList(TTupleDef& _Types) noexcept : STypeList(type_seq, _Types) {}
    STypeList(STypeList&& _Move) noexcept : STypeList(type_seq, _Move.infos) {}
    STypeList(const STypeList& _Copy) noexcept : STypeList(type_seq, _Copy.infos) {}

private:
    // Final constructor initializing infos. TListIndices is the index_sequence for the filtered and flattened tuple, while TType is the unfiltered flattened tuple from the constructors above
    template <size_t... TListIndices, typename... TTypes>
    STypeList(std::index_sequence<TListIndices...>, std::tuple<TTypes...>&& _Types) noexcept
        : infos{ make_tuple_no_duplicates(std::move(_Types)) }
    { }

    // Final constructor initializing infos via copy
    template <size_t... TListIndices>
    STypeList(std::index_sequence<TListIndices...>, const TTupleDef& _Infos) noexcept
        : infos(_Infos)
    { }
};

// Test Code:
struct STestType1 { };
struct STestType2 { };

static inline auto TypeListBase = STypeList
(
    STypeUnknown()
    ,SType<bool>()
    ,SType<float>()
    ,SType<double>()
    ,SType<int>()
    ,SType<long>()
    ,SType<char>() //<- comment in to produce error
);

static inline auto TypeListA = STypeList
(
    TypeListBase
    ,SType<STestType1>()
);

static inline auto TypeListB = STypeList
(
    TypeListBase
    ,SType<STestType2>()
);

static inline auto TypeListAB = STypeList
(
    TypeListA,
    TypeListB
);

int main()
{

}

尝试编译时输出将是(使用 Visual Studio 2017,gcc 和 VS2019 中似乎也出现了问题):

1>------ Build started: Project: VsTest, Configuration: Debug x64 ------
1>VsTest.cpp
1>XXX\vstest.cpp(20): fatal error C1060: compiler is out of heap space
1>Done building project "VsTest.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
4

2 回答 2

1

我看到的问题是你filter_duplicate的递归特化,永远遵循两种情况:InTestOutTypes...列表中的情况和不在列表中的情况。

这迫使编译器遵循很多死案例。

建议:将递归特化拆分为两个递归特化:版本“在列表中”和“不在列表中”版本。

我建议一个额外的帮助模板

template <typename, typename>
struct with_duplicate : public std::false_type
 { };

template <typename OTuple, typename IT0, typename ... ITs>
struct with_duplicate<OTuple, std::tuple<IT0, ITs...>>
   : public contains<IT0, OTuple>
 { };

现在filter_duplicates声明,带有一个额外的模板默认参数(基于with_duplicate

template <typename Out, typename In, bool = with_duplicate<Out, In>::value>
struct filter_duplicates;

地面案例几乎保持不变(只是添加了第三个模板参数)

// ground case
template <typename Out>
struct filter_duplicates<Out, std::tuple<>, false>
 {
   using filtered_tuple_t = Out;
   static constexpr Out& filter(Out&& _In) { return _In; }
 };

现在递归案例“在列表中”

// with duplicate case
template <typename... OutTypes, typename InTest, typename... InRest>
struct filter_duplicates<std::tuple<OutTypes...>,
                         std::tuple<InTest, InRest...>,
                         true>
 {
   using filter_t = filter_duplicates<std::tuple<OutTypes...>,
                                      std::tuple<InRest...>>;

   using filtered_tuple_t = typename filter_t::filtered_tuple_t;

   static constexpr auto
      filter (std::tuple<OutTypes..., InTest, InRest...>&& _In)
    { return filter_seq(
          std::make_index_sequence<sizeof...(OutTypes)>{},
          std::make_index_sequence<sizeof...(InRest)>{},
          std::move(_In)); }

   template <std::size_t... TIndicesOut,
             std::size_t... TIndicesIn, typename... In>
   static constexpr auto filter_seq (std::index_sequence<TIndicesOut...>,
                                     std::index_sequence<TIndicesIn...>,
                                     std::tuple<In...>&& _In)
    { return filter_t::filter(std::make_tuple(
             std::move(std::get<TIndicesOut>(_In))...,
             std::move(std::get<sizeof...(TIndicesOut) + 1u + TIndicesIn>(_In))...)); }
 };

和递归案例“不在列表中”

// without duplicate case
template <typename... OutTypes, typename InTest, typename... InRest>
struct filter_duplicates<std::tuple<OutTypes...>,
                         std::tuple<InTest, InRest...>,
                         false>
 {
   using filter_t = filter_duplicates<std::tuple<OutTypes..., InTest>,
                                      std::tuple<InRest...>>;

   using filtered_tuple_t = typename filter_t::filtered_tuple_t;

   static constexpr auto
      filter (std::tuple<OutTypes..., InTest, InRest...>&& _In)
    { return filter_seq(
          std::make_index_sequence<sizeof...(OutTypes)>{},
          std::make_index_sequence<sizeof...(InRest) + 1u>{},
          std::move(_In)); }

   template <std::size_t... TIndicesOut,
             std::size_t... TIndicesIn, typename... In>
   static constexpr auto filter_seq (std::index_sequence<TIndicesOut...>,
                                     std::index_sequence<TIndicesIn...>,
                                     std::tuple<In...>&& _In)
    { return filter_t::filter(std::make_tuple(
             std::move(std::get<TIndicesOut>(_In))...,
             std::move(std::get<sizeof...(TIndicesOut) + TIndicesIn>(_In))...)); }
 };

不确定filter()filter_seq()方法中的所有索引是否正确(检查自己),但是这样编译器应该避免很多无用的实现。

在我的 Linux 平台中,拆分递归专业化,我已将编译时间(clang++ 和 g++,依次)从 51",1 减少到 1",6。没有确切的数据与内存消耗有关,但我可以说这大大减少了。

于 2020-02-11T17:59:56.583 回答
1

而现在……为了完全不同的东西……

如何避免递归序列filter()filter_seq()使用可变参数列表进行调用,该列表bool说明哪些元素提取,哪些是重复项?

with_duplicate我的意思是......在另一个答案中给出了相同的助手类

template <typename, typename>
struct with_duplicate : public std::false_type
 { };

template <typename OTuple, typename IT0, typename ... ITs>
struct with_duplicate<OTuple, std::tuple<IT0, ITs...>>
   : public contains<IT0, OTuple>
 { };

我提出filter_duplicate一个稍微复杂一点的声明

template <typename Out, typename In,
          typename = std::integer_sequence<bool>,
          typename = std::index_sequence<>,
          bool = with_duplicate<Out, In>::value>
struct filter_duplicates;

现在两个递归案例,案例“在列表中”和“不在列表中”,它们只是从旧filter_t类型继承而来

template <typename... OutTypes, typename InTest, typename... InRest,
          typename Is, bool ... Bs>
struct filter_duplicates<std::tuple<OutTypes...>,
                         std::tuple<InTest, InRest...>,
                         std::integer_sequence<bool, Bs...>,
                         Is, true>
   : public filter_duplicates<std::tuple<OutTypes...>,
                              std::tuple<InRest...>,
                              std::integer_sequence<bool, Bs..., true>,
                              std::make_index_sequence<sizeof...(Bs)+1u>>
 { };

template <typename... OutTypes, typename InTest, typename... InRest,
          typename Is, bool ... Bs>
struct filter_duplicates<std::tuple<OutTypes...>,
                         std::tuple<InTest, InRest...>,
                         std::integer_sequence<bool, Bs...>,
                         Is, false>
   : public filter_duplicates<std::tuple<OutTypes..., InTest>,
                              std::tuple<InRest...>,
                              std::integer_sequence<bool, Bs..., false>,
                              std::make_index_sequence<sizeof...(Bs)+1u>>
 { };

现在复杂的部分是接收可变参数列表的基本情况,bool原始元组中的每种类型都有一个Bs...,其中true“重复类型”和false“无重复类型”

// ground case
template <typename Out, bool ... Bs, std::size_t ... Is>
struct filter_duplicates<Out, std::tuple<>,
                         std::integer_sequence<bool, Bs...>,
                         std::index_sequence<Is...>, false>
 {
   using filtered_tuple_t = Out;

   template <bool B, std::size_t I, typename Tpl>
   static constexpr auto getTplCond (Tpl && t)
    {
      if constexpr ( B ) // duplicate
         return std::tuple<>{};
      else // not duplicate
         return std::tuple{std::get<I>(t)};
    }

   template <typename ... Ts>
   static constexpr std::enable_if_t<sizeof...(Ts) == sizeof...(Bs), Out>
       filter (std::tuple<Ts...> && _In)
    { return std::tuple_cat( getTplCond<Bs, Is>(_In)... ); }
 };

这样,您可以调用一个filter()非递归但递归继承的方法。

也是filtered_type_t递归继承的。

于 2020-02-11T20:06:20.213 回答