Use a std::vector to initialize part of another std::vector

Viewed 166

Is there a way to "expand" a vector variable in an initialization list to achieve something to the effect of

std::vector<int> tmp1{1,1,2,3,5};
std::vector<int> tmp2{11,tmp1,99};
//tmp2 == {11,1,1,2,3,5,99}

I know it could be done with std::copy or insert. Was wondering if there was list-initialization way achieve that. something like [11,*tmp1,99] in python

3 Answers

With this solution you can have something close the the syntax you want. (And yes some of the work will be done at runtime)

#include <type_traits>
#include <vector>

namespace details
{
// recursive part
template<typename type_t, typename arg_t, typename...  args_t>
void append_to_vector(std::vector<type_t>& vec, arg_t value, args_t&&... values)
{
    if constexpr (std::is_same_v<arg_t, std::vector<type_t>>)
    {
        vec.insert(vec.end(), value.begin(), value.end());
    }
    else
    {
        vec.push_back(value);
    }

    if constexpr (sizeof...(args_t) > 0)
    {
        append_to_vector(vec, std::forward<args_t>(values)...);
    }
}
}

template<typename type_t, typename...  args_t>
std::vector<type_t> make_vector(args_t&&... values)
{
    std::vector<type_t> retval;
    if constexpr (sizeof...(args_t) > 0)
    {
        details::append_to_vector(retval, std::forward<args_t>(values)...);
    }
    return retval;
};

int main()
{
    auto tmp1 = make_vector<int>(1, 1, 2, 3, 5);
    auto tmp2 = make_vector<int>(11, tmp1, 99);
    return 0;
}

One option could be to create a make_vector variadic function template that checks if the current argument supports std::begin(). If it supports std::begin(), use the vector's insert member function, otherwise, use emplace_back. That should make it possible to make a vector from any container (with the correct T).

First, one type trait to check if the current argument supports std::begin() and one that checks if it supports std::size(). std::size() will be used to calculate how much space the final vector will need. Reserving space is often used to avoid reallocating space (and moving elements) when populating vectors when the final (or minimal) amount of elements is known - as it is in this case.

#include <iterator>
#include <type_traits>

template<typename T>
class has_begin {
    template<typename TT>
    static auto test(int) -> 
        decltype( std::begin(std::declval<const TT&>()), std::true_type() );

    template<typename>
    static auto test(...) -> std::false_type;

public:
    static constexpr bool value = decltype(test<T>(0))::value;
};

template<class T>
inline constexpr bool has_begin_v = has_begin<T>::value;
template<typename T>
class has_size {
    template<typename TT>
    static auto test(int) ->
        decltype( std::size(std::declval<const TT&>()), std::true_type() );

    template<typename>
    static auto test(...) -> std::false_type;

public:
    static constexpr bool value = decltype(test<T>(0))::value;
};

template<class T>
inline constexpr bool has_size_v = has_size<T>::value;

Then, the actual make_vector function template and helpers to calculate the actual size and determine what to do with one specific argument.

#include <utility>
#include <vector>

namespace detail {

template<class T, class Arg>
size_t make_vector_capacity(Arg&& arg) {
    if constexpr(std::is_same_v<T, std::remove_cv_t<std::remove_reference_t<Arg>>>) {
        // same type as in the vector, return 1
        return 1;
    } else if constexpr(has_size_v<Arg>) {
        // a container supporting std::size
        return std::size(arg);
    } else if constexpr(has_begin_v<Arg>) {
        // a container but not supporting std::size
        return std::distance(std::begin(arg), std::end(arg));
    } else {
        // fallback
        return 1;
    }
}

template<class T, class Arg>
void make_vector_helper(std::vector<T>& v, Arg&& arg) {
    if constexpr(std::is_same_v<T, std::remove_cv_t<std::remove_reference_t<Arg>>>) {
        // same type as in the vector, insert it as-is
        v.emplace_back(std::forward<Arg>(arg));
    } else if constexpr(has_begin_v<Arg>) {
        // arg supports std::begin, use insert
        v.insert(v.end(), std::begin(arg), std::end(arg));
    } else {
        // fallback
        v.emplace_back(std::forward<Arg>(arg));
    }
}
} // namespace detail
template<class T, class... Args>
std::vector<T> make_vector(Args&&... args) {
    std::vector<T> rv;

    // a fold expression to calculate the capacity needed:
    rv.reserve( (detail::make_vector_capacity<T>(args) + ...) );

    // a fold expression to call make_vector_helper for each argument
    (detail::make_vector_helper(rv, std::forward<Args>(args)), ...);

    return rv;
}

A usage example, mixing different containers and single values of std::string:

#include <initializer_list>
#include <iostream>
#include <list>
#include <vector>
#include <string>

int main() {
    using namespace std::string_literals;

    const std::string arr[] = {"3"s, "4"s};
    const std::vector vec{"5"s, "6"s, "7"s , "8"s};
    const std::list list{"9"s,"10"s};
    auto init = {"13"s, "14"s, "15"s};

    const auto tmp2 = make_vector<std::string>("1"s, "2"s, arr, vec, list,
                                               "11"s, "12"s, init );

    for(const auto& e: tmp2) std::cout << e <<' ';
}

Output:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 

Demo

No, it is not possible but you can make a helper that allows can do it:

#include <array>
#include <type_traits>

// Containerize MaybeContainer type if it not equal to T.
template<typename T, typename MaybeContainer>
using impl_containerize =  std::conditional_t<std::is_same_v<T,std::decay_t<MaybeContainer>>,std::array<T,1>,MaybeContainer>;

// Concatenate containers
template<typename Container,typename...Args>
Container impl_construct(Args&&...args){
    Container c;
    (c.insert(c.end(), std::begin(args), std::end(args)), ...);
    return c;
}

template<typename Container,typename...Args>
Container construct(Args&&...args){
    using T = typename Container::value_type;
    return impl_construct<Container, impl_containerize<T,Args>...>({std::forward<Args>(args)}...);
}

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> tmp1{1,3,5,7,9};
    const auto tmp2 = construct<std::vector<int>>(2,4,tmp1,6);
    for(const auto& e: tmp2){
        std::cout<<e<<' ';
    }
}

Output

2 4 1 3 5 7 9 6 
Related