创建 const std::vector 作为两个 const std::vector 的串联

Create const std::vector as the concatenation of two const std::vector

提问人:Manumerous 提问时间:11/9/2022 最后编辑:Manumerous 更新时间:11/10/2022 访问量:326

问:

我想创建一个包含另外两个相同类型的所有元素。由于向量是 我不能使用连接两个 std::vectors 中提到的方法将其与两者逐步连接起来。const std::vectorconst std::vectorconstconst std::vector

#include <iostream>
#include <vector>

int main()
{
    const std::vector<int> int_a{0,1};
    const std::vector<int> int_b{2,3};
    const std::vector<int> all_ints;
    
    for (int i: all_ints)
        std::cout << i << ' ';
    return 0;
}

对于上面的示例,我想以某种方式定义输出是 .all_ints0 1 2 3

这怎么能做到呢?

C++ 模板 STL 常量 声明

评论

1赞 463035818_is_not_an_ai 11/9/2022
你试过什么吗?你是否因为 ?如果这是问题所在,请使用非常量向量,用其他两个向量的值填充它,然后使用它来初始化常量向量。const
4赞 Ashraful Alam Shakil 11/9/2022
这回答了你的问题吗?连接两个 std::vector
1赞 463035818_is_not_an_ai 11/9/2022
@AshrafulAlamShakil房间里的大象是.我真的不确定 OP 是否根本不知道如何连接,或者是否只有制造了斗争。constconst
0赞 Manumerous 11/9/2022
感谢您关于删除 .我同意这将使它变得更容易。我希望这个问题专门关于常量向量的定义,所以我知道这一点,并且当我在整个程序中使用它们时保持不变。constint_aint_btotal_ints

答:

6赞 Aykhan Hagverdili 11/9/2022 #1

创建一个函数,该函数接受其他两个向量,创建第三个向量,插入前两个向量的值,按值返回结果。然后将其分配给您的常量向量:

const std::vector<int> int_a{0,1};
const std::vector<int> int_b{2,3};
const std::vector<int> all_ints = concat(int_a, int_b);

评论

0赞 joergbrech 11/9/2022
range-v3 提供了这样的功能:godbolt.org/z/9WP68anx4。它不确定这个功能是否会在 c++23 中可用,所以最好不要依赖它很快成为标准库的一部分。
3赞 Saeed Amiri 11/9/2022 #2

我实际上不知道创建这样的常量向量的本质是什么。但是一个简单的技巧是创建一个临时的非常量向量,并用前两个向量填充它,然后创建最终的常量向量。例如:

const std::vector<int> int_a{0,1};
const std::vector<int> int_b{2,3};
std::vector<int> middle(int_a);
middle.insert(middle.begin(),int_b.begin(),int_b.end());
const std::vector<int> all_ints(middle);

正如评论中建议的那样,最后一行可以写成:

const std::vector<int> all_ints = std::move(middle);

评论

0赞 Aykhan Hagverdili 11/9/2022
您也可以移动以避免复制。middle
0赞 Aykhan Hagverdili 11/9/2022
您还可以将此串联业务移动到 lambda 中,这样临时业务就不必停留在周围:godbolt.org/z/cbro6cx11
0赞 Saeed Amiri 11/9/2022
这实际上不太可读(这不是 lambda 最后的目的)。
0赞 Aykhan Hagverdili 11/9/2022
这样可以避免出现冗余的临时停留。当然,最优化的选择是创建一个使用这些向量调用的独立 concat 函数。
0赞 Saeed Amiri 11/9/2022
我认为你自己的答案实际上很好,但在这种情况下,lambda 不太合适。
1赞 Fareanor 11/9/2022 #3

正如 @Ayxan Haqverdili 的回答中已经提到的,您可以创建一个用于初始化向量的串联函数。

对于这样的功能,我建议以下实现:

template <template <typename, typename> typename C, typename ... Args>
C<Args...> concat(const C<Args...> & lhs, const C<Args...> & rhs)
{
    C<Args...> res(lhs.cbegin(), lhs.cend());
    res.insert(res.cend(), rhs.cbegin(), rhs.cend());
    return res;
}

注意:此实现泛化到除 之外的所有标准库序列容器std::array

然后可以像这样使用:

const std::vector<int> a {1, 2, 3};
const std::vector<int> b {4, 5};
    
const std::vector<int> ab = concat(a, b);

现场示例在这里


另一种更简单的版本可能是:

template <typename C>
C concat(const C & lhs, const C & rhs)
{
    C res(lhs.size() + rhs.size());
    typename C::iterator it = std::copy(lhs.cbegin(), lhs.cend(), res.begin());
    std::copy(rhs.cbegin(), rhs.cend(), it);
    return res;
}

现场示例在这里

评论

0赞 Aykhan Hagverdili 11/9/2022
可以简化模板,使其不使用参数包 godbolt.org/z/EKKo3zE3b
0赞 Fareanor 11/9/2022
@AyxanHaqverdili 哦,你是对的,我无缘无故地把事情复杂化了:o唯一的问题是它的使用是特定于(但既然问题问的是,这是公平的:))reserve()std::vectorstd::vector
0赞 Fareanor 11/9/2022
@AyxanHaqverdili我编辑了问题,并根据您的评论添加了替代版本。感谢您的观察:)
0赞 Aykhan Hagverdili 11/9/2022
请注意,您的版本值初始化 的元素,然后用 和 中的值覆盖它们。这要求类型具有可访问的默认构造函数,还引入了初始化开销。我的版本通过使用 + 来避免这两个问题。reslhsrhsreserveinsert
0赞 Fareanor 11/9/2022
是的,实际上我尽量避免使用,因为我想让它也与其他序列容器兼容。但它似乎有一种或另一种方式的成本 XDreserve()
0赞 MarkB 11/10/2022 #4

如果您只是想遍历两个向量,则可以使用自定义视图连接器来实现。以下操作比创建(和销毁)另一个容器只是为了在两个范围内进行迭代要有效得多。

#include <iostream>
#include <array>
#include <ranges>
#include <vector>
#include <utility>

template<typename T1, typename T2>
auto concat_view(T1& lhs, T2& rhs)
{
    static_assert(std::is_same_v<std::decay_t<T1>, std::decay_t<T2>>);
    using T1_ = std::remove_reference_t<T1>;
    using T2_ = std::remove_reference_t<T2>;
    if constexpr (std::is_const_v<T1_> || std::is_const_v<T2_>)
    {
        using Iter = typename std::decay_t<T1>::const_iterator;
        return std::array<std::ranges::subrange<Iter>, 2>{std::as_const(lhs), std::as_const(rhs)} | std::views::join;
    }
    else
    {
        using Iter = typename std::decay_t<T1>::iterator;
        return std::array<std::ranges::subrange<Iter>, 2>{lhs, rhs} | std::views::join;
    }    
}

int main()
{
    std::vector<int> v1{1,2,3}, v2{4,5,6};
    for (int& val : concat_view(v1, v2))
        ++val;
    for (const auto& val : concat_view(std::as_const(v1), v2))
        std::cout << val << '\n';
    return 0;
}

评论

0赞 MarkB 11/10/2022
注意:Range-V3 优先于此实现
1赞 doug 11/10/2022 #5

这是另一个实现,也可以很容易地修改,甚至可以在将来修改的内容。它不需要最新的 c++ 版本。all_ints

#include <vector>
#include <algorithm>

int main()
{
    const std::vector<int> int_a{ 0,1 };
    const std::vector<int> int_b{ 2,3 };
    const std::vector<int> all_ints(int_a.size()+ int_b.size());
    std::vector<int>& all_intsr = const_cast<std::vector<int>&>(all_ints);
    std::copy(int_b.begin(), int_b.end(), std::copy(int_a.begin(), int_a.end(), all_intsr.begin()));
}

这利用了能够合法地将 a 转换为 a 并具有以下限制来防止 UB 的优势。不得修改矢量对象。这不包括向量所拥有的内容,它不是 const。既不修改矢量对象,也不修改矢量对象。以后修改它的元素也是合法的,例如这样。const vector&vectorbegin()end()

all_intsr[3]=42;