有条件地删除折叠表达式中的函数调用

问题描述

我知道我可以使用 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>()) 应该生成一个数组,其中包含包含 charshortint 的组的视图。我在下面创建了一个 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...>()

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

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。

Live example

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...