基于身份的相等的有序关联容器

时间:2017-07-07 05:54:52

标签: c++ templates

我正在编写模板函数,模板参数为X。在该函数内部,我想创建std::set<std::pair<int, X>>,以便:

  • 该集合中的对象按对的第一个(int)字段排序(我不关心如何破坏关系)
  • 我可以添加多个具有相同.first的对象,只要他们的.second不相同

如果我知道模板参数X始终定义<,那么最基本的std::set<std::pair<int, X>(使用默认比较器)将完全正常。不幸的是,我不能假设X

我在考虑“欺骗”并使用基于指针的X字段比较:

template <typename X>
struct DistCmp {
    bool operator()(const std::pair<int, X>& lhs, const std::pair<int, X>& rhs) const {
        return lhs.first < rhs.first || lhs.first == rhs.first && &lhs.second < &rhs.second;
    }
};

template <typename X>
void f() {
    std::set<std::pair<int, X>, DistCmp<X>> s{};
    // ...
}

(毕竟,我并不关心如何 .second进行比较,只要不对不相同的对象进行同等比较。)

不幸的是,我不认为这是正确的。部分是因为C ++标准here的引用(它表明指针比较,一般来说,未指定,因此我不能依赖它们对于不相同的对象不相等)。部分我只觉得它是可疑的/黑客的。

针对此问题是否有任何干净/便携的解决方案?

更新

我想到的一种方法是使用==来比较指针而不是<。但是,这并不好,因为它会导致pair<1, x> < pair<1, y> && pair<1, y> < pair<1, x>true。这违反了strict weak ordering的要求,可能会导致stuff to break

2 个答案:

答案 0 :(得分:-1)

简短的回答:使用std :: multiset和sidestep整个问题 - multiset允许多个键,所以只需比较对的第一个元素。

注意,如果您想在地图中禁止使用相同的X值,则需要向X添加要求,因此它至少是EqualityComparable。否则,当X的值相同且不同时,您甚至无法检测到。

更长的回答: 您的代码不会产生您希望的结果。考虑添加新对&lt; 0,x&gt;使用&lt; 0,x&gt;进行映射。 std :: map将尝试查找&lt; 0,x&gt;的存在使用&lt; 0,x&gt;的临时副本。它将使用x的地址(临时!),它会将false与所有内容进行比较,即在map中,std :: map将根据x的地址(临时!)找到要插入的位置。然后它会复制x,从而改变地址并可能破坏它自己的顺序。

答案 1 :(得分:-1)

我认为::std::map< int, ::std::set< X > >符合您的两个标准。

更新:由于可能没有<运算符来比较X实例,您可能需要编写具有专业化的模板:

#include <map>
#include <set>
#include <vector>
#include <type_traits>
#include <algorithm>
#include <utility>

//  Default implementation is used when there is no < operator to compare T instances,
//  but there is == operator.
template< typename T, typename TDummy = void >
t_FancyContainerHelper final
{
    public: using
    t_Values = ::std::vector< T >;

    public: using
    t_IntToValues = ::std::map< int, t_Values >;

    public: static void
    Insert(t_IntToValues & int_to_values, int const key, T const & value)
    {
        auto p_pair{int_to_values.find(key)};
        if(int_to_values.end() != p_pair)
        (
            auto const & values(p_pair->second);
            if(values.end() != ::std::find(values.begin(), values.end(), value))
            {
                return;
            }
        }
        else
        {
            p_pair = int_to_values.emplace
            (
                ::std::piecewise_construct
            ,   ::std::forward_as_tuple(key)
            ,   ::std::forward_as_tuple()
            ).second;
        }
        auto & values(p_pair->second);
        values.emplace(value);
    }
};

//  This specialization is used when there is < operator to compare T instances.
template< typename T > class
t_FancyContainerHelper
<
    T
,   ::std::enable_if_t
    <
        ::std::is_same
        <
            bool
        ,   decltype
            (
                ::std::declval< T const & >()
                <
                ::std::declval< T const & >()
            )
        >
    >
> final
{
    public: using
    t_Values = ::std::set< T >;

    public: using
    t_IntToValues = ::std::map< int, t_Values >;

    public: static void
    Insert(t_IntToValues & int_to_values, int const key, T const & value)
    {
        auto p_pair{int_to_values.find(key)};
        if(int_to_values.end() != p_pair)
        (
            auto const & values(p_pair->second);
            if(values.end() != values.find(value))
            {
                return;
            }
        }
        else
        {
            p_pair = int_to_values.emplace
            (
                ::std::piecewise_construct
            ,   ::std::forward_as_tuple(key)
            ,   ::std::forward_as_tuple()
            ).second;
        }
        auto & values(p_pair->second);
        values.emplace(value);
    }
};