2

I would like to create a const std::vector to contain all the elements of two other const std::vector of the same type. Since the vector is const I can not concatenate it step by step with the two const std::vector using the method mentioned in Concatenating two std::vectors.

#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;
}

For the example above I would like to define all_ints in a way that the output is 0 1 2 3.

How could that be done?

Manumerous
  • 455
  • 6
  • 21
  • 1
    did you try something? Are you struggling because of the `const` ? If thats the issue then use a non-const vector, fill it with values from the other two, then use that to initialize the const vector. – 463035818_is_not_an_ai Nov 09 '22 at 13:41
  • 4
    Does this answer your question? [Concatenating two std::vectors](https://stackoverflow.com/questions/201718/concatenating-two-stdvectors) – Ashraful Alam Shakil Nov 09 '22 at 13:49
  • 1
    @AshrafulAlamShakil the elephant in the room is `const`. I am really not sure if OP doesnt know how to concatenate at all, or if it is only the `const` that creates the struggle. – 463035818_is_not_an_ai Nov 09 '22 at 13:55
  • Thanks for your suggestion about removing the `const`. I agree that would make it easier. I wanted this question to be specifically about the definition of a const vector though so I know that `int_a`, `int_b` and `total_ints` stay constant when I use them throughout my program. – Manumerous Nov 09 '22 at 13:59

5 Answers5

6

Make a function that takes the other two vectors, creates a third one, inserts values from the first two, returns the result by value. Then assign this to your const vector:

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);
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • range-v3 provides such a function: https://godbolt.org/z/9WP68anx4. Its unsure if this functionality will even be available in c++23, so best not depend on this becoming part of the standard library soon. – joergbrech Nov 09 '22 at 14:35
3

I actually don't know what's the essence of creation of const vectors like this. But a simple hack is to create a temporary non-const vector and fill it with the first two vectors, then create the final const vector. eg:

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);

As suggested in comments, the last line could be written as:

const std::vector<int> all_ints = std::move(middle);
Manumerous
  • 455
  • 6
  • 21
Saeed Amiri
  • 22,252
  • 5
  • 45
  • 83
1

As already mentioned in @Ayxan Haqverdili's answer, you can create a concatenation function that will be used to initialize your vector.

I propose the following implementation for such a function:

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;
}

Note: This implementation is generalized to all standard library sequence containers except std::array.

Which can then be used like this:

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

Live example here


An alternative and simpler version could be:

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;
}

Live example here

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • Your template can be simplified to not use parameter packs https://godbolt.org/z/EKKo3zE3b – Aykhan Hagverdili Nov 09 '22 at 14:17
  • @AyxanHaqverdili Oh you're right, I overcomplicated things for no reason :o The only thing is the use of `reserve()` which is specific to `std::vector` (but since the question asks about `std::vector`, it's fair :) ) – Fareanor Nov 09 '22 at 14:21
  • @AyxanHaqverdili I edited the question and added an alternative version based on your comments. Thanks for the observation :) – Fareanor Nov 09 '22 at 14:32
  • Note that your version value-initializes elements of `res` and then overwrites them with values from `lhs` and `rhs`. This requires the type to have an accessible default constructor, also introduces the initialization overhead. My version avoids both of these issues by using `reserve` + `insert` instead. – Aykhan Hagverdili Nov 09 '22 at 14:36
  • Yes indeed, actually I tried to avoid to use `reserve()` since I wanted to have it compatible with other sequence containers too. But it seems it has a cost one way or another XD – Fareanor Nov 09 '22 at 14:39
  • Otherwise there is no point of using a template if only one single type is eligible to be used. – Fareanor Nov 09 '22 at 14:46
  • There is always a way to avoid all unnecessary costs in the most generic way in C++ ;-) https://godbolt.org/z/r1TbYE1rv – Aykhan Hagverdili Nov 09 '22 at 14:48
  • @AyxanHaqverdili Ahah right, this should be what I should have come with XD Well done ! – Fareanor Nov 09 '22 at 14:50
1

Here's yet another implementation that can also easily be modified to even modify the contents of all_ints at a future time. It does not require recent c++ versions.

#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()));
}

This takes advantage of a being able to legally cast a const vector& to a vector with the following restriction to prevent UB. One may not modify the vector object. This does not include the contents owned by the vector which is not const. Neither begin() or end() modify the vector object. Also modifying element of it later are legal such as this.

all_intsr[3]=42;
doug
  • 3,840
  • 1
  • 14
  • 18
0

If you are just looking to iterate over both vectors, you can do it using a custom view concatenator. The following is much more efficient than creating (and destroying) another container just to iterate over both ranges.

#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;
}
MarkB
  • 672
  • 2
  • 9