问题描述
我知道我可以使用 SFINAE 来禁用基于条件的模板化函数的生成,但这在这种情况下并不起作用。我想在编译时初始化一个数组,该数组应包含与条件匹配的值。像这样:
template <std::size_t i,class ... Types,class ... Group>
constexpr auto fetch_match(const std::tuple<Group...>& candidates)
{
if constexpr (is_match<std::tuple<Group...>,i,Types...>())
{
auto& group = std::get<i>(candidates);
return group.template get<Types...>();
}
}
template <class ... Types,class ... Group,std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates,std::index_sequence<indices...>)
{
constexpr std::array views {
(fetch_match<indices,Types...>(candidates),...),};
return views;
}
我知道上面的代码是错误的,无法编译。如果条件未填充,那么我希望折叠表达式不生成该函数调用。我该怎么做?
这个问题可能是一个 XY 问题,所以这里有一个更详细的问题。
我有一个 Registry
,其中包含 Group
的异构数据。我希望能够查询包含指定类型子列表的所有组。例如,for (const auto& view : registry.get<char,short,int>())
应该生成一个数组,其中包含包含 char
、short
和 int
的组的视图。我在下面创建了一个 mcve。当前代码的问题是我必须先创建数组,然后复制视图,我想避免这种情况。
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T,typename... Ts>
constexpr bool contains = (std::is_same<T,Ts>{} || ...);
template <typename Subset,typename Set>
constexpr bool is_subset_of = false;
template <typename... Ts,typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>,std::tuple<Us...>> = (contains<Ts,Us...> && ...);
template <typename ... T>
struct View
{
const char* name_of_group; // For debugging.
std::tuple<T...> data;
};
template <typename ... Ts>
struct Group
{
using type_set = std::tuple<Ts...>;
static const char* name; // For debugging.
std::tuple<Ts...> data;
explicit Group(Ts... values) : data(values...) {}
template <typename ... Us>
[[nodiscard]] View<Us...> get() const noexcept
{
return { this->name,std::make_tuple(std::get<Us>(this->data)...) };
}
};
template <class Groups,std::size_t i,class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i,Groups>;
bool match = is_subset_of<std::tuple<Types...>,typename group_type::type_set>;
return match;
}
template <std::size_t i,class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates,Array& matches,std::size_t& index)
{
if constexpr (is_match<std::tuple<Group...>,Types...>())
{
auto& group = std::get<i>(candidates);
matches[index++] = group.template get<Types...>();
}
}
template <class ... Types,std::index_sequence<indices...>)
{
constexpr std::size_t size = (is_match<std::tuple<Group...>,indices,Types...>() + ... + 0);
std::array<View<Types...>,size> views {};
std::size_t index = 0;
(add_matches<indices,Types...>(candidates,views,index),...);
return views;
}
template <typename ... Group>
class Registry
{
public:
explicit Registry(Group... groups) : groups(groups...) {}
template <typename ... T>
auto get()
{
constexpr auto indices = std::index_sequence_for<Group...>{};
return get_matches<T...>(this->groups,indices);
}
private:
std::tuple<Group...> groups;
};
using A = Group<char>;
using B = Group<char,short>;
using C = Group<char,int>;
using D = Group<char,int,long long>;
// Giving the classes names for debugging purposes.
template<> const char* A::name = "A";
template<> const char* B::name = "B";
template<> const char* C::name = "C";
template<> const char* D::name = "D";
int main()
{
auto registry = Registry(A{0},B{1,1},C{2,2,2},D{3,3,3});
// Should yield an array of size 2 with View<char,int>,// one from group C and one from Group D.
for (const auto& view : registry.get<char,int>())
{
std::cout << "View of group: " << view.name_of_group << std::endl;
std::cout << "char: " << int(std::get<char>(view.data)) << std::endl;
std::cout << "short: " << std::get<short>(view.data) << std::endl;
std::cout << "int: " << std::get<int>(view.data) << std::endl;
}
}
template <class Groups,typename group_type::type_set>;
return match;
}
template <class ... Types,std::size_t ... indices>
constexpr auto build_view_array(const std::tuple<Group...>& candidates,std::index_sequence<indices...>)
{
std::array views {
std::get<indices>(candidates).template get<Types...>()...
};
return views;
}
template <std::size_t i,class Groups,class TypeSet,std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups,TypeSet>())
return std::index_sequence<x...,i>{};
else
return std::index_sequence<x...>{};
}
template <std::size_t i,std::size_t j,std::size_t ... rest,TypeSet>())
return get_matching_indices<j,rest...,Groups,TypeSet,x...>();
else
return get_matching_indices<j,x...>();
}
template <class ... Types,std::index_sequence<indices...>)
{
constexpr auto matching_indices = get_matching_indices<indices...,std::tuple<Group...>,std::tuple<Types...>>();
constexpr auto views = build_view_array<Types...>(candidates,matching_indices);
return views;
}
感觉应该可以了,但是由于以下错误无法编译:
/Users/tedkleinbergman/Programming/ECS/temp.cpp:76:39: error: no matching function for call to 'get_matching_indices'
constexpr auto matching_indices = get_matching_indices<indices...,std::tuple<Types...>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char,Group<char>,Group<char,short>,long long>,1,3>' requested here
return get_matches<T...>(this->groups,indices);
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:118:38: note: in instantiation of function template specialization 'Registry<Group<char>,long long> >::get<char,int>' requested here
for (const auto& view : registry.get<char,int>())
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:57:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Groups'
constexpr auto get_matching_indices()
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:65:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'rest'
constexpr auto get_matching_indices()
^
1 error generated.
解决方法
首先,从 index_sequence
过滤器开始:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t,I>;
template<std::size_t I>
constexpr index_t<I> index = {};
template<std::size_t...Is,std::size_t...Js>
constexpr std::index_sequence<Is...,Js...> concatenate( std::index_sequence<Is...>,std::index_sequence<Js...> ) {
return {};
}
template <class Test>
constexpr auto filter_sequence(std::index_sequence<> sequence,Test test) {
return sequence;
}
template<std::size_t I0,std::size_t...Is,class Test>
constexpr auto filter_sequence( std::index_sequence<I0,Is...>,Test test )
{
constexpr auto tail = filter_sequence( std::index_sequence<Is...>{},test );
if constexpr ( test(index<I0>) ) {
return concatenate( std::index_sequence<I0>{},tail );
} else {
return tail;
}
}
然后我们使用这些原语。
template <class Group,class ... Types>
constexpr auto get_match_indexes()
{
constexpr auto test = [](auto I){ return is_match<Group,I,Types...>(); };
constexpr auto indexes = std::make_index_sequence< std::tuple_size_v<Group> >{};
constexpr auto retval = filter_sequence( indexes,test );
return retval;
}
template<class ... Types,class Group,std::size_t...Is>
std::array<sizeof...Is,View<Types...>> get_matches(const Group& candidates,std::index_sequence<Is...> ) {
return {{
std::get<Is>(candidates).template get<Types...>(),...
}};
}
template<class ... Types,class Group>
std::array<sizeof...Is,View<Types...>> get_matches(const Group& candidates ) {
return get_matches<Types...>( candidates,get_match_indexes<Group,Types...>() );
}
或类似的东西。
请注意,某些编译器可能需要将 is_match<Group,Types...>()
替换为 is_match<Group,decltype(I)::value,Types...>()
。
可能有错别字。这至少使用 c++17。
filter_sequence
使用 O(n^2) 模板符号长度和 O(n) 递归模板实例化深度。可以通过一个棘手的代码将其改进为 O(n lg n) 长度和 O(lg n) 深度;基本上,您需要在中间将 Is...
拆分为 As...
和 Bs...
并以这种方式递归。
这是一个索引序列的日志深度分割:
template<class A,class B>
struct two_things {
A a;
B b;
};
template<class A,class B>
two_things(A,B)->two_things<A,B>;
template<class Seq>
constexpr auto split_sequence( index_t<0>,Seq seq ) {
return two_things{ std::index_sequence<>{},seq };
}
template<std::size_t I0,std::size_t...Is>
constexpr auto split_sequence( index_t<1>,std::index_sequence<I0,Is...> seq ) {
return two_things{ std::index_sequence<I0>{},std::index_sequence<Is...>{} };
}
template<std::size_t N,class Seq>
constexpr auto split_sequence( index_t<N>,Seq seq ) {
constexpr auto step1 = split_sequence( constexpr_index<N/2>,seq );
constexpr auto step2 = split_sequence( constexpr_index<N-N/2>,step1.b );
return two_things{ concatenate(step1.a,step2.a),step2.b };
}
template<std::size_t...Is>
constexpr auto halve_sequence( std::index_sequence<Is...> seq ) {
return split( index< (sizeof...(Is)) / 2u >,seq );
}
(two_things
以比标准元组或对轻很多很多倍的形式存在。
这反过来又可以让您改进过滤器序列。
template<std::size_t I,class Test>
constexpr auto filter_sequence( std::index_sequence<I> seq,Test test )
{
if constexpr ( test(constexpr_index<I>) ) {
return seq;
} else {
return std::index_sequence<>{};
}
}
template<std::size_t...Is,class Test>
constexpr auto filter_sequence( std::index_sequence<Is...> seq,Test test )
{
constexpr auto split = halve_sequence( seq );
constexpr auto head = filter_sequence( split.a,test );
constexpr auto tail = filter_sequence( split.b,test );
return concatenate(head,tail);
}
这个版本应该编译得更快并且使用更少的内存,特别是对于大量元素。但是你应该从上面更简单的开始,因为(正如我所指出的)可能有很多 tpyos。