从类型列表中递归删除重复项会导致编译器堆空间错误 (VS2017)

Recursive duplicate removal from a type list causes compiler out heap space error (VS2017)

本文关键字:编译器 空间 错误 VS2017 列表 类型 递归 删除      更新时间:2023-10-16

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

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

该代码从其他类型列表和类型构造平展类型列表,并删除重复项,以便为列表中注册的每个类型提供唯一 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>XXXvstest.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 ==========

而现在...对于完全不同的东西...

如何避免使用可变参数列表的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是递归继承的。

我看到的问题是,您的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。没有确切的数据与内存消耗有关,但我可以说这大大减少了。