【问题标题】:Distingushing STL containers区分 STL 容器
【发布时间】:2013-06-11 06:04:57
【问题描述】:

经常,我想为不同的 STL 容器专门化一个函数。但是我不想一一专门化,因为它们中的一些共享大部分需要的接口,例如 std::vector 和 std::deque。

在我的用例中,主要有三个类别(vector-like、set-like、map-like)。 例如,我想实现类似的东西,

template <class T>
struct A {
    template <class Y, class... Z>
    void func( Y y , Z... z ){
        //hypothetical static_if
        static_if ( T is similar to vector, deque, or boost::stable_vector etc which have push_back ) {
            t.push_back(y);
        }
        else static_if ( T is similar to set, unordered_set or boost::flat_set etc which have emplace)    {
            t.emplace(y);
        }

        else static_if ( T is similar to map, unordered_map or boost::flat_map etc which has emplace) {
            t.emplace(y, z...);
        }

    }

    T t;  
};

我认为这似乎是不可能的,但我希望有一些针对这种情况的 hack。 如果它可以扩展到列表类型(std::list, std::forward_list, ...) 或 boost::heap 或其他,那也很好。然而,实现目标似乎太难了。

【问题讨论】:

  • 目标是什么?在哪些情况下向量、集合和映射可以互换?
  • 您可以创建类型特征并为每个类似 xxx 的类型设置一个函数特化。
  • 这可以通过专业化和类型特征来完成,但看起来你在重新发明轮子,因为标准算法提供了很多功能和通用接口
  • @larsmans:现在我正在为图形实现邻接列表。用于存储边缘邻居的第二个容器可以是我项目中的向量或集合或地图。
  • insert() 的重载适用于任何事情,也许可以使用它。

标签: c++ templates c++11 containers


【解决方案1】:

这并非不可能,只是实施起来不方便或微不足道。真的,需要有一个container_traits 库。

这可以使用 SFINAE 来检测某个类型的Container 是否包含满足各种 STL Container Concepts 要求的必要功能。

例如,is_associative_container 可以使用 SFINAE 来检查 Container 类型是否具有 key_typemapped_type 类型定义,以及 std::pair&lt;const key_type, map_type&gt; 用于 value_type,以及对象的所有其他要求实现了AssociativeContainer 概念。

无论如何,这确实需要作为一个成熟的库来实现。实施起来并非易事,如果您有简单的一次性需求,您可能应该寻找另一种解决方案。您最好在 Iterator 级别进行抽象。

【讨论】:

  • 那么,不是有一个著名的库,它有类似于is_associative_container 的东西吗?
  • 不需要 container_traits 库。它的不存在在一定程度上阻止了糟糕的设计,就像提议的设计一样。
【解决方案2】:

这是一个用于容器的粗略类型特征库。

template<typename Container>
struct container_traits;

template<bool b=true>
struct has_emplace_back { typedef std::integral_constant<bool, b> emplace_back; };
template<bool b=true>
struct has_emplace { typedef std::integral_constant<bool, b> emplace; };

template<typename T, typename A>
struct container_traits< std::vector<T,A> > : has_emplace_back<>, has_emplace<> {};
// etc
template<typename T, typename A>
struct container_traits< std::set<T,A> > : has_emplace_back<false>, has_emplace<> {};
// etc

template<typename T>
using HasEmplaceBack = typename container_traits<T>::has_emplace_back;
template<typename T>
using HasEmplace = typename container_traits<T>::has_emplace;

template<int> struct enum_enum { enum class type {}; };
template<int index> using UniqueEnum = typename enum_enum<index>::type;

template<bool b, int index=1>
using EnableIf = typename std::enable_if< UniqueEnum<index> >::type;
template<bool b, int index=1>
using DisableIf = EnableIf< b, -index >;

template<typename Container, typename... Args, EnableIf< HasEmplace<Container>::value && !HasEmplaceBack<Container>::value, 1 >... >
void emplace_in( Container&& c, Args&&... args ) {
  std::forward<Container>(c).emplace( std::forward<Args>(args)... );
}
template<typename Container, typename... Args, EnableIf< HasEmplaceBack<Container>::value, 2 >... >
void emplace_in( Container&& c, Args&&... args ) {
  std::forward<Container>(c).emplace_back( std::forward<Args>(args)... );
}

EnableIf&lt;&gt;... 技术在 clang 中不起作用,我没有编译它,所以它可能需要一些调试来修复。

【讨论】:

  • 谢谢。实际上,我无法完全理解您的代码,尤其是对于 UniqueEnum。无论如何,我猜 std::vector 既有 emplace 又有 emplace_back。
  • @Sungmin 好点——尽管emplace 不同。 “有无定位的地方”是罗嗦的。 UniqueEnum&lt;x&gt; 是每个 xEnableIfDisableIf 的独特空 enum class 类型,如果 bool 为真,则评估为此类 UniqueEnum 类型。我将这些UniqueEnum 的(空)可变参数包作为类型参数传递给emplace_in,以确保两个templates 具有不同的template 签名。我会稍微修复一下容器特征,以使错误案例起作用......
  • 谢谢。这看起来很漂亮。:) 所以,我必须在container_trait 中枚举所有可能的容器作为模板参数,对吗?
  • @sungmin emplace 无法检测到没有参数。 Emplace with arguments 即可。所以可以写一个特征can_emplace_back_with&lt;T,Args...&gt;,连同can_emplace_nakedly。这有点危险,因为如果 Args... 恰好是 2 或更长,并且第一个 arg 可以转换为 iterator,您可能会在 vector 上得到一个意想不到的 emplace
【解决方案3】:

一周前我尝试了复杂的特征,但为了更好的可读性。

请看这里的例子:Is there a better way to check if a STL container is a multi* container

【讨论】:

  • 谢谢,所以你的意思是我必须专攻所有可能的课程?我想这似乎是最干净的方式。
  • 这取决于您必须追求的特征。就我而言,我必须区分组集、地图和多...
【解决方案4】:

不,对不起。不要这样做。 Scott Meyers 的Effective STL 中有一个完整的部分专门介绍了这一点。以下摘录可让您了解这是多么有问题:

然后,假设您渴望编写可与 最常见的序列容器:vector、deque 和 list。清楚地, 您必须针对他们的能力进行编程,并且 表示不使用储备或容量(参见第 14 条),因为 deque 和 列表不提供它们。 list 的存在也意味着你放弃了 operator[],并且你将自己限制在双向迭代器的能力上。反过来,这意味着你必须远离那些 需求随机访问迭代器,包括 sort、stable_sort、 partial_sort 和 nth_element(参见条款 31)。

另一方面,您对支持向量的渴望排除了使用 push_front 和 pop_front,vector 和 deque 都将 kibosh 放在 splice 和 sort 的成员形式。结合上述限制,后一种禁止意味着没有任何形式的 sort 你可以调用你的“广义序列容器”。

这是显而易见的东西。如果您违反任何这些限制,您的 代码将无法使用至少一个您想要的容器进行编译 能够使用。将编译的代码更加隐蔽。

罪魁祸首是迭代器失效的不同规则, 适用于不同序列容器的指针和引用。 要编写可以正确使用向量、双端队列和列表的代码,您 必须假定任何使迭代器、指针或 任何这些容器中的引用都会使它们在容器中无效 你正在使用。因此,您必须假设每次调用 insert 都会使所有内容无效,因为 deque::insert 会使所有迭代器无效,并且, 由于缺乏调用容量的能力,vector::insert 必须被假定为 使所有指针和引用无效。 (第 1 项说明 deque 是 有时会使其迭代器无效而不使其无效 指针和引用。)类似的推理得出结论 必须假定每次对擦除的调用都会使所有内容无效。

想要更多? [是的,这个项目一直在,一直在,一直在。]

【讨论】:

  • 谢谢,我不知道。:)。
猜你喜欢
  • 2012-01-27
  • 2011-07-24
  • 2015-09-20
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多