【问题标题】:C++ generic insert into std container?C++ 通用插入标准容器?
【发布时间】:2016-05-22 19:24:17
【问题描述】:

如果我有以下程序:

#include <vector>
#include <set>

template<class T, class U>
void AddToContainer(T& container, U value)
{
  container.push_back(value);
}

int main(char**, int)
{
   std::vector<int> v;
   AddToContainer(v, 1);

   std::set<int> s;
   AddToContainer(s, 1);

   return 0;
}

我怎样才能使添加到容器通用?由于std::set 没有push_back 而只有insert,因此编译将失败。

【问题讨论】:

  • 这是听起来是个好主意,但实际上从来都不是。
  • STL 已经拥有尽可能多的静态多态性 - 毕竟,这是设计目标之一。您的建议只是鼓励您的AddToContainer 的客户效率低下。

标签: c++ c++11 containers


【解决方案1】:

您可以使用带有虚拟参数的表达式 SFINAE 来检查 push_back() 是否有效:

template <class C, class V>
auto append(C& container, V&& value, int)
    -> decltype(container.push_back(std::forward<V>(value)), void())
{
    container.push_back(std::forward<V>(value));
}

template <class C, class V>
void append(C& container, V&& value, ...)
{
    container.insert(std::forward<V>(value));
}

您的函数将转发到哪个:

template <class C, class V>
void AddToContainer(C& container, V&& value) {
    append(container, std::forward<V>(value), 0);
}

如果push_back() 是一个有效的表达式,则首选第一个重载,因为int... 更适合0 如果push_back() 不是一个有效的表达式,那么只有一个可行的超载。


这是否真的是一个好主意是一个单独的问题。

【讨论】:

  • 在这两种情况下都可以使用 insert-with-end()
  • @T.C.如果提示不好,这对std::set 究竟意味着什么?
  • 效率稍低。它与无提示插入具有相同的复杂性,但您可能需要支付额外的比较费用。
  • 您应该添加 insert-with-end() 作为答案,它远没有这个复杂,而且这个答案需要一些 C++17 的东西(标记为 C++11)
  • @paulm 这个答案不需要任何 C++17 的东西。
【解决方案2】:

我相信所有* C++ 容器(尽管不是像 priority_queue 这样的容器适配器)都有一个如下所示的插入版本:

iterator insert(iterator location, T&& value)

对于序列集合,位置是实际位置;对于关联集合(如mapunordered_map),迭代器是一个“提示”参数(例如,如果您已经知道它在排序顺序中的确切位置,则可以帮助map 快速插入一个元素)。但是,提供无效提示不会导致任何无效行为,因此 C++ 集合的有效泛型插入将是:

template<C, T>
void insert(C& collection, T&& value) {
    collection.insert(collection.end(), std::forward<T>(value));
}

* 貌似forward_list是唯一没有这个方法的,有道理。

【讨论】:

  • “如果你唯一的工具是锤子,那么每个问题看起来都像钉子。” ——马克吐温
【解决方案3】:

C++20 风格:

template<typename C, typename V>
  requires requires (C& c, V&& v) { c.push_back(std::forward<V>(v)); }
auto AddToContainer(C& container, V&& value) {
  return container.push_back(std::forward<V>(value));
}

template<typename C, typename V>
  requires (requires (C& c, V&& v) { c.insert(c.end(), std::forward<V>(v)); } &&
    !requires(C& c, V&& v) { c.push_back(std::forward<V>(v)); })
auto AddToContainer(C& container, V&& value) {
  return container.insert(container.end(), std::forward<V>(value));
}

或更简洁但诊断更差:

template<typename C, typename V>
auto AddToContainer(C& container, V&& value)
{
  if constexpr (requires (C& c, V&& v) { c.push_back(std::forward<V>(v)); })
    return container.push_back(std::forward<V>(value));

  else
    return container.insert(container.end(), std::forward<V>(value));
}

【讨论】:

    【解决方案4】:

    如果您担心插入集合中的元素顺序,

    template<typename Container, typename value>
    void addelement(Container& C, value v)
    {
        std::fill_n(std::inserter(C,C.end()), 1,v);
    }
    
    int main()
    {
        std::vector<int> v;
        addelement(v, 2);
        addelement(v, 4);
        addelement(v, 6);
    
        std::set<int> s;
        addelement(s, 8);
        addelement(s, 6);
        addelement(s, 4);
        addelement(s, 8);
    
        std::cout << "Vector elements :: " << std::endl;
        for (auto item : v)
            std::cout << item << std::endl;
    
        std::cout << "Set elements :: " << std::endl;
        for (auto item : s)
            std::cout << item << std::endl;
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-31
      • 1970-01-01
      相关资源
      最近更新 更多