2

我知道我可以使用 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>())应该产生一个数组,其中包含包含 和 的组charshort视图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 ... Types, class ... Group, class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates, Array& matches, std::size_t& index)
{
    if constexpr (is_match<std::tuple<Group...>, i, Types...>())
    {
        auto& group = std::get<i>(candidates);
        matches[index++] = 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::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, short, int>;
using D = Group<char, short, 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,3});

    // Should yield an array of size 2 with View<char, short, int>,
    // one from group C and one from Group D.
    for (const auto& view : registry.get<char, short, 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, 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 <class ... Types, class ... Group, 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, i, 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, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
    if constexpr (is_match<Groups, i, TypeSet>())
        return get_matching_indices<j, rest..., Groups, TypeSet, i, x...>();
    else
        return get_matching_indices<j, rest..., Groups, TypeSet, x...>();
}

template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, 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<Group...>, std::tuple<Types...>>();
                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char, short, int, Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> , 0, 1, 2, 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>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> >::get<char, short, int>' requested here
    for (const auto& view : registry.get<char, short, 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.
4

1 回答 1

1

首先,从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, I, Types...>()is_match<Group, decltype(I)::value, Types...>().

可能有错别字。这至少使用

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。

活生生的例子

于 2021-01-02T05:25:54.547 回答