【问题标题】:Compare two sets of types for equality比较两组类型的平等
【发布时间】:2017-02-27 14:40:15
【问题描述】:

如何检查两个参数包是否相同,而忽略它们的内部顺序?

到目前为止,我只有框架(使用std::tuple),但没有功能。

#include <tuple>
#include <type_traits>

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

template <typename ... Types1, typename ... Types2>
struct type_set_eq<std::tuple<Types1...>, std::tuple<Types2...>>
    : std::true_type
{
    // Should only be true_type if the sets of types are equal
};

int main() {
    using t1 = std::tuple<int, double>;
    using t2 = std::tuple<double, int>;
    using t3 = std::tuple<int, double, char>;

    static_assert(type_set_eq<t1, t1>::value, "err");
    static_assert(type_set_eq<t1, t2>::value, "err");
    static_assert(!type_set_eq<t1, t3>::value, "err");
}

每个类型都不允许在一个集合中出现多次。

【问题讨论】:

  • 哪个限制对你更重要,时间还是记忆?你可以修改序列吗?
  • 一种可能的(慢速)方法:从第一个元组中弹出x,在第二个元组中查找x,如果找到则删除。重复直到第一个元组为空或在第二个元组中找不到x
  • 更棒的是:该函数还可以“返回”(即定义第二个 typedef)整数序列,允许将第一个元组重新映射到第二个。如果它们完全匹配,这将允许从另一种元组类型构造一个元组类型,这听起来像是这个问题的可能最终应用。
  • 如果 get&lt;T&gt; 对 SFINAE 友好,那么会有一个更简洁的解决方案。
  • &lt;int,int,float&gt; 是否等于 &lt;float,int&gt;?我的意思是,您是否不仅要忽略顺序或值,还要忽略它们的出现次数?

标签: c++ c++11 templates variadic-templates template-meta-programming


【解决方案1】:

如果元组中的类型是唯一的,您可以利用继承来回答是否第一个元组中的所有类型都作为辅助结构的基础。例如。 (C++11 方法):

#include <tuple>
#include <type_traits>

template <class T>
struct tag { };

template <class... Ts>
struct type_set_eq_helper: tag<Ts>... { };

template <class, class, class = void>
struct type_set_eq: std::false_type { };

template <bool...>
struct bool_pack { };

template <bool... Bs>
using my_and = std::is_same<bool_pack<Bs..., true>, bool_pack<true, Bs...>>;

template <class... Ts1, class... Ts2>
struct type_set_eq<std::tuple<Ts1...>, std::tuple<Ts2...>, typename std::enable_if< (sizeof...(Ts1) == sizeof...(Ts2)) && my_and< std::is_base_of<tag<Ts2>, type_set_eq_helper<Ts1...>>::value...  >::value  >::type  >:
   std::true_type { };

int main() {
    using t1 = std::tuple<int, double>;
    using t2 = std::tuple<double, int>;
    using t3 = std::tuple<int, double, char>;

    static_assert(type_set_eq<t1, t1>::value, "err");
    static_assert(type_set_eq<t1, t2>::value, "err");
    static_assert(!type_set_eq<t1, t3>::value, "err");
}

[Live demo]

【讨论】:

  • 不错的解决方案,尤其是带有'tag-ellipsis'的想法
  • 多集示例不起作用。它认为tuple&lt;int&gt; 等于tuple&lt;int, int&gt;
  • @Barry 好吧,这真的取决于平等的定义。是的,我假设 tuple&lt;int&gt; 等于 tuple&lt;int, int&gt; 并且这里的行为是预期的(由我)。我将尝试根据您的假设调整方法,即它不是...
  • @W.F.这就是多重​​集定义相等性的方式。
  • @Barry 所以你建议我应该更改答案中的措辞或者我应该找到另一个解决方案?...
【解决方案2】:

Boost.Hana 解决方案:

constexpr auto t1 = hana::tuple_t<int, double>;
constexpr auto t2 = hana::tuple_t<double, int>;
constexpr auto t3 = hana::tuple_t<int, double, char>;

auto same = [](auto a, auto b)
{
    auto to_occurrences_map = [](auto t)
    {
        return hana::fold(t, hana::make_map(), [](auto m, auto x)
        { 
            if constexpr(!hana::contains(decltype(m){}, x)) 
            { 
                return hana::insert(m, hana::make_pair(x, 1)); 
            }
            else { return ++(m[x]); }
        });
    };

    return to_occurrences_map(a) == to_occurrences_map(b);
};

static_assert(same(t1, t1));
static_assert(same(t1, t2));
static_assert(!same(t1, t3));

live wandbox example

【讨论】:

  • @akappa:我坚信它可能会更漂亮,只是需要花更多时间在上面:)
  • 想知道为什么sameto_occurences_map 没有标记为constexpr?是编译器自己解决的吗?
  • @akappa:在 C++17 中,如果可以的话,lambdas 隐含地是 constexpr
  • @akappa:不,但它不需要太多就可以与 C++14 一起使用。只需将if constexpr 替换为类似的静态分支工具hana 提供一个) 并在适当的地方使用decltype(lambda()){} 从非constexpr lambda 的返回值中获取常量表达式。
【解决方案3】:

目前尚不清楚 OP 是否要关心出现次数(如主题建议 - “无序列表”,或不 - 如 type_set_eq 建议。

所以,我将介绍这两种变体。


从集合开始-出现次数不重要,那么算法如下:

  1. 对于 T1 中的每种类型,检查它是否存在于 T2 中
  2. 对于 T2 中的每种类型,检查它是否存在于 T1 中

这两点都很重要 - 因为只检查点 1 时 - 我们有空 T1 列表的反例,它等于任何值,当然,点 2 的反例相同(这些点是对称的)。

要检查某个类型列表中是否存在一种类型 - 使用这个简单的类模板:

template <typename V, typename ...T> struct is_present;

template <typename V> // <- type is not present in empty list
struct is_present<V> : std::false_type {};

template <typename V, typename F, typename ...T> 
struct is_present<V,F,T...> : std::integral_constant<bool,
// type is present in non-empty list
// if it is first element
std::is_same<V,F>::value 
//  or it is present in the remaining list-but-first
|| is_present<V,T...>::value> {};

由于我们处于 C++17 之前的时代 - 那么fold expression 尚不可用,因此需要下面这样的内容来表示折叠和:

template <bool ...v> struct all_trues;
template <> struct all_trues<> : std::true_type {};
template <bool f, bool ...v> struct all_trues<f,v...> : 
    std::integral_constant<bool, 
                           f && all_trues<v...>::value> 
{};

那么比较两组类型是否相等的定义如下:

template <typename ...T1>
struct are_set_of_types 
{
    template <typename ...T2> 
    struct equal_to : all_trues<is_present<T1, T2...>::value...,  /*1*/
                                is_present<T2, T1...>::value...>  /*2*/
   {};

};

可以用std::tuple作为OP开始实现,这样:

template <typename T1, typename T2>
struct type_set_eq;
template <typename ...T1, typename ...T2>
struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>> 
      : are_set_of_types <T1...>::template equal_to<T2...> 
{};

当出现次数很重要时,算法如下:

  1. 检查两个序列的大小是否相等
  2. 检查左序列中每个值的出现次数是否等于第二序列中该值的出现次数

这两点应该保证序列是相等的,而不考虑元素的顺序。

所以,与 set-comparison 的区别在于这个类模板:

template <typename V, typename ...T>
struct count_occurences;

template <typename V> 
// number of occurrences in empty list is 0
struct count_occurences<V> : std::integral_constant<std::size_t, 0u> {};

template <typename V, typename F, typename ...T> 
// number of occurrences in non-empty list is 
struct count_occurences<V,F,T...> : std::integral_constant<std::size_t, 
// 1 if type is same as first type (or 0 otherwise)
(std::is_same<V,F>::value ? 1u : 0u) 
// plus number of occurrences in remaining list
+ count_occurences<V,T...>::value> {};

以及检查两个序列是否相等的模板:

template <typename ...T1>
struct are_unordered_types_sequences 
{
    // when number of occurrences is important
    template <typename ...T2> 
    struct equal_to : all_trues<
            /*1*/ sizeof...(T1) == sizeof...(T2), 
            /*2*/ (count_occurences<T1, T1...>::value == count_occurences<T1, T2...>::value)...>
   {};
};

和元组变体:

template <typename T1, typename T2>
struct type_set_eq;
template <typename ...T1, typename ...T2>
struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>> 
      : are_unordered_types_sequences<T1...>::template equal_to<T2...> 
{};

Wandbox example I used to play with these templates

【讨论】:

  • 列表的大小无关紧要,因为它们代表一个集合,即重复不应该改变语义。
  • @Columbo 好吧,你只能从这个 OP // should only be true_type if the sets of types are equal 评论中猜到。无论如何 - 我会问的。
  • 不,我没猜。如果 OP 想要一个多重集,他会这样称呼它。直言不讳地假设他想要多集行为是一个糟糕的默认值。
  • @Columbo 我不同意:"compare two unordered type lists for equality " - 比较两个无序列表(不是集合),不考虑内部顺序是众所周知的操作,并不意味着出现的次数无关紧要
  • 很公平(我没有彻底阅读这个问题,只是从他的模板名称推断出来的)。但是,我也没有否决您的回答,我只是提出一个观点。
【解决方案4】:

当然不是最好的解决方案,但我们可以一次只选择一种类型,看看它是否在另一个列表中。如果我们没有找到它,它们就不是平等的。如果我们这样做,请重复两个较小的列表:

template <class A, class B>
struct type_set_eq : std::false_type { };

// base case: two empty packs are equal
template <>
struct type_set_eq<std::tuple<>, std::tuple<>> : std::true_type { };

template <class Lhs, class Done, class Rhs>
struct type_set_eq_impl;

// at least one element in each - we use the middle type to keep track 
// of all the types we've gone through from the Ys
template <class X, class... Xs, class... Ds, class Y, class... Ys>
struct type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds...>, std::tuple<Y, Ys...>>
    : std::conditional_t<
        std::is_same<X,Y>::value,
        type_set_eq<std::tuple<Xs...>, std::tuple<Ds..., Ys...>>,
        type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds..., Y>, std::tuple<Ys...>>>
{ };

// if we run out, we know it's false
template <class X, class... Xs, class... Ds>
struct type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds...>, std::tuple<>>
    : std::false_type
{ };

template <class... Xs, class... Ys>
struct type_set_eq<std::tuple<Xs...>, std::tuple<Ys...>>
    : std::conditional_t<
        (sizeof...(Xs) == sizeof...(Ys)),
        type_set_eq_impl<std::tuple<Xs...>, std::tuple<>, std::tuple<Ys...>>,
        std::false_type>
{ };

// shortcut to true
template <class... Xs>
struct type_set_eq<std::tuple<Xs...>, std::tuple<Xs...>>
    : std::true_type 
{ };

【讨论】:

    【解决方案5】:

    在 C++17 中,我们可以使用折叠表达式相当简单地解决这个问题:

    template <typename T, typename... Rest>
    constexpr bool is_one_of_v = (std::is_same_v<T, Rest> || ...);
    
    // Given:
    // typename... Types1, typename... Types2
    constexpr bool is_same_set_v = 
        // |{Types1...}| == |{Types2...}| and {Types1...} subset of {Types2...}:
        sizeof...(Types1) == sizeof...(Types2)
        && (is_one_of_v<Types1, Types2...> && ...);
    
    // Alternative if you want to allow repeated set elements; more mathematical:
    constexpr bool is_same_set_v = 
        // {Types1...} subset of {Types2...} and vice versa.
        (is_one_of_v<Types1, Types2...> && ...)
        && (is_one_of_v<Types2, Types1...> && ...);
    

    向后移植到 C++14 很简单:

    template <bool...> struct bools {};
    
    template <bool... Vs>
    constexpr bool all_of_v = std::is_same<bools<true, Vs...>, bools<Vs..., true>>::value;
    
    template <bool... Vs>
    constexpr bool any_of_v = !all_of_v<!Vs...>;
    
    template <typename T, typename... Rest>
    constexpr bool is_one_of_v = any_of_v<std::is_same<T, Rest>::value...>;
    
    // Given:
    // typename... Types1, typename... Types2
    constexpr bool is_same_set_v = 
        // |{Types1...}| == |{Types2...}| and {Types1...} subset of {Types2...}:
        sizeof...(Types1) == sizeof...(Types2)
        && all_of_v<is_one_of_v<Types1, Types2...>...>;
    
    // Alternative if you want to allow repeated set elements; more mathematical:
    constexpr bool is_same_set_v = 
        // {Types1...} subset of {Types2...} and vice versa.
        all_of_v<is_one_of_v<Types1, Types2...>...>
        && all_of_v<is_one_of_v<Types2, Types1...>...>;
    

    降级到 C++11 可以通过将这些模板变量更改为通过结构来完成,如下所示:

    template <bool... Vs>
    struct all_of {
        static constexpr bool value =
            std::is_same<bools<true, Vs...>, bools<Vs..., true>>::value;
    };
    

    【讨论】:

      猜你喜欢
      • 2017-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-12
      相关资源
      最近更新 更多