【问题标题】:Concatenate two std::vectors in one line using C++11使用 C++11 在一行中连接两个 std::vectors
【发布时间】:2017-07-03 13:41:05
【问题描述】:

有没有办法使用 C++11 在一个代码行中连接两个 std::vectors,第一个定义在局部变量中,第二个从函数返回:

#include <vector>
#include <iostream>
#include <iterator>

std::vector<int> getNewVector()
{
    return {4,5,6};
}

int main(int argc, char** argv)
{
  std::vector<int> dest;
  std::vector<int> src{1,2,3};

  dest = src + getNewVector(); //Error: no operator "+" matches these operands

  return 0;
}

编辑:this question 不同,它不使用 C++11,我想知道新的 C++11 标准是否提供了一些有用的功能来帮助我完成任务。 例如,我使用了 + 运算符,即使它不起作用,但只是为了说明我在寻找什么。

【问题讨论】:

  • 这不是上述问题的重复。 '+' 运算符对于 std::vector 是未定义的。使用insert 方法完成连接。 IE。 dest.insert(dest.end(),src.begin(),src.end());
  • @GavinPortwood dest.insert(dest.end(),getNewVector().begin(),getNewVector()‌​.end()); 是 UB,因为迭代器 getNewVector().begin()getNewVector()‌​.end() 不属于同一个容器。
  • @AlgirdasPreidžius 但是你在哪里插入 src ?
  • 或者,您可以只定义一个全局operator+,它将两个标准向量作为输入,而不使用任何派生类。

标签: c++ c++11 stdvector


【解决方案1】:

您可以使用 boost::range 库轻松完成此操作。

范围将成为 C++ 标准库的一部分。详情请见Ranges for the Standard Library

加分项:结果向量只分配一个内存。

#include <vector>
#include <boost/range/join.hpp>

int main() {
    std::vector<int> a{1,2,3};
    std::vector<int> b{4,5,6};
    auto c = boost::copy_range<std::vector<int>>(boost::join(a, b));
}

或者,将其推广到两个以上的输入序列和类型:

template<class T, class U>
auto join(T const& a, U const& b) -> decltype(boost::join(a, b)) {
    return boost::join(a, b);
}

template<class T, class U, class... Args>
auto join(T const& a, U const& b, Args const&... args) -> decltype(boost::join(a, join(b, args...))) {
    return boost::join(a, join(b, args...));
}

int main() {
    std::vector<int> a{1,2,3};
    std::list<int> b{4,5,6};
    std::set<int> c{7,8,9};
    auto d = boost::copy_range<std::vector<int>>(join(a, b, c)); 
}

同样,它只在boost::copy_range&lt;std::vector&lt;int&gt;&gt; 中分配一次内存,因为输入序列的长度是已知的。

【讨论】:

    【解决方案2】:

    我们可以利用一个左值和右值被传递给operator+的事实:

    #include <vector>
    #include <iostream>
    #include <iterator>
    #include <algorithm>
    
    std::vector<int> getNewVector()
    {
        return {4, 5, 6};
    }
    
    template <class T>
    T operator+(const T& l, T&& r)
    {
        T c{};
        c.reserve(l.size() + r.size());
        auto bi = std::back_inserter(c);
        std::copy(l.begin(), l.end(), bi);
        std::move(r.begin(), r.end(), bi);
        return c;
    }
    
    int main()
    {
        std::vector<int> dest;
        std::vector<int> src{1, 2, 3};
        dest = src + getNewVector(); //uses operator "+"
        return 0;
    }
    

    【讨论】:

    • 您应该将元素移动到c,而不是复制它们。
    • operator+ 的这个实现中存在不必要的内存重新分配。
    • @Rakete1111 是的,搬家会更好。对int 没有影响,但对于模板会更好。
    • @21koizyd 标准库头文件允许包含其他头文件。不能保证没有&lt;algorithm&gt; 也能正常工作,所以请包含您使用的内容。
    • @rex 我担心如果你传入std::vector&lt;std::string&gt;&gt;,你的代码会从左边的参数中吸取教训。这是一个令人惊讶的副作用。
    【解决方案3】:

    重载运算符 + 采用 2x 向量,没有库提升的解决方案

    #include <vector>
    #include <iostream>
    template<typename T>
    std::vector<T> operator+(std::vector<T> vec1, const std::vector<T>&& vec2)
    {
        vec1.insert(vec1.end(), vec2.begin(), vec2.end());
        return vec1;
    }
    std::vector<int> getNewVector()
    {
        return{ 4,5,6 };
    }
    template<typename T>
    void displayVectors(const std::vector<T>& src, const std::vector<T>& dest, const std::vector<T>&& getNewVector)
    {
        std::cout << "src { ";
        for (const auto& itr : src)
            std::cout << itr << " ";
        std::cout << "}" << std::endl;
        std::cout << "getNewVector() { ";
        for (const auto& itr : getNewVector)
            std::cout << itr << " ";
        std::cout << "}" << std::endl;
        std::cout << "dest { ";
        for (const auto& itr : dest)
            std::cout << itr << " ";
        std::cout << "}" << std::endl;
    }
    int main(int argc, char** argv)
    {
        std::vector<int> dest;
        std::vector<int> src{ 1,2,3 };
        displayVectors(src, dest, getNewVector());
        dest = src + getNewVector();
        std::cout << "After operation" << std::endl;
        displayVectors(src, dest, getNewVector());
        return 0;
    }
    

    或使用移动语义

    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <iterator>
    template<typename T>
    std::vector<T> operator+(std::vector<T> vec1, const std::vector<T>&& vec2)
    {
        std::move(vec2.begin(), vec2.end(), std::back_inserter(vec1));
        return vec1;
    }
    std::vector<int> getNewVector()
    {
        return{ 4,5,6 };
    }
    template<typename T>
    void displayVectors(const std::vector<T>& src, const std::vector<T>& dest)
    {
        std::cout << "src { ";
        for (const auto& itr : src)
            std::cout << itr << " ";
        std::cout << "}" << std::endl;
        std::cout << "dest { ";
        for (const auto& itr : dest)
            std::cout << itr << " ";
        std::cout << "}" << std::endl;
    }
    int main(int argc, char** argv)
    {
        std::vector<int> dest;
        std::vector<int> src{ 1,2,3 };
        displayVectors(src, dest);
        dest = src + getNewVector();
        std::cout << "After operation" << std::endl;
        displayVectors(src, dest);
        return 0;
    }
    

    【讨论】:

      【解决方案4】:
      namespace named_operator {
        template<class D>struct make_operator{make_operator(){}};
      
        template<class T, char, class O> struct half_apply { T&& lhs; };
      
        template<class Lhs, class Op>
        half_apply<Lhs, '+', Op> operator+( Lhs&& lhs, make_operator<Op> ) {
          return {std::forward<Lhs>(lhs)};
        }
      
        template<class Lhs, class Op, class Rhs>
        auto operator+( half_apply<Lhs, '+', Op>&& lhs, Rhs&& rhs )
        -> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
        {
          return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
        }
      }
      

      先是样板库,然后:

      namespace ops {
        struct concat_t:named_operator::make_operator<concat_t>{};
        static const concat_t concat{};
        template<class T, class A, class A2>
        std::vector<T,A> named_invoke( std::vector<T,A> lhs, concat_t, std::vector<T,A2> const& rhs){
          lhs.insert(lhs.end(), rhs.begin(), rhs.end());
          return std::move(lhs);
        }
      }
      using ops::concat;
      

      编写+concat+ 运算符。最终用途如下:

      int main(){
         std::vector<int> a{1,2,3};
         std::vector<int> b{7,8,9};
      
        for( auto x: a +concat+ a +concat+ b )
          std::cout <<x<<'\n';
      }
      

      Live example.

      std 中重载裸+ 是非法的,并且在根命名空间中是脆弱/危险的。由于“在子命名空间中不起作用”而变得脆弱,如果你让它过于贪婪,就会很危险。 concat_t 标签类型避免两者。

      谁想调用一个函数。带有()s 前缀的符号链很烦人。

      上面复制了左侧(除非 lhs 是临时的),然后连接 rhs。将 move-contents 添加到 rhs 是命名空间 ops 中的另一个命名调用函数。所以a+a+b 复制a,然后将副本扩展两次。

      表达式模板版本可以避免因常见问题而多次调整大小。

      【讨论】:

      • static const concat_t concat{}; 有点重言式,因为const 足以使它成为static
      【解决方案5】:

      根据 Maxim Egorushkin 的建议

      a.insert(a.end(), b.begin(), b.end());
      

      会更有效率。

      如果你想修改'b'的值并插入到'a'中,请使用std::transform,下面的代码

      std::transform( b.begin(), b.end(), std::back_inserter(a), [](int i)->int   { return i + delta; } );
      

      完整代码

      int main()
      {
          std::vector<int> a = {1,2,3,4,5};
          std::vector<int> b = {11,22,33,44,55};   
      
      //to copy
       a.insert(a.end(), b.begin(), b.end());
      
      //to transform
          std::transform( b.begin(), b.end(), std::back_inserter(a), [](int i)->int   { return i + delta; } );
      
          return 0;
      }
      

      【讨论】:

      • 如果您不打算“转换”这些值,为什么还要打扰std::transformstd:copy 不是更有意义吗?
      • a.insert(a.end(), b.begin(), b.end()); 打字和阅读效率更高,时间更短。
      猜你喜欢
      • 2010-09-17
      • 2021-02-02
      • 2015-04-17
      • 2017-10-15
      • 2020-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多