使用std::vector初始化另一个std::vector的一部分

3
有没有一种方法可以在初始化列表中“展开”一个向量变量,以达到类似以下的效果:
std::vector<int> tmp1{1,1,2,3,5};
std::vector<int> tmp2{11,tmp1,99};
//tmp2 == {11,1,1,2,3,5,99}

我知道可以通过std::copy或insert来实现。不过想知道是否有类似python中[11,*tmp1,99]的列表初始化方式实现该功能。


6
不,无法以其他方式实现。您必须将“11”、“tmp1”和“99”作为单独的操作插入。 - Remy Lebeau
1
如果你阅读了有关std::vector::insert的内容,你可以想出类似于std::vector<int> tmp2{11, 99}; tmp2.insert(std::next(tmp2.begin()), tmp1.begin(), tmp1.end());这样的代码。 - Ted Lyngmo
3个回答

6

通过这个解决方案,您可以拥有接近您所需语法的内容。(是的,一些工作将在运行时完成)

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

3
一个选项是创建一个变长参数函数模板make_vector,检查当前参数是否支持std::begin()。如果它支持std::begin(),则使用vectorinsert成员函数;否则,使用emplace_back。这样就可以从任何容器(具有正确的T)创建一个向量。

首先,一个类型特性用于检查当前参数是否支持std::begin(),另一个类型特性则用于检查其是否支持std::size()std::size()将被用来计算最终vector所需的空间大小。在这种情况下,为了避免在填充向量时重新分配空间(并移动元素),通常使用保留空间来确保已知最终(或最小)元素数量。

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

接下来是实际的make_vector函数模板及其帮助程序,用于计算实际大小并确定如何处理一个特定的参数。

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

一个使用示例,混合不同的容器和单个 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 <<' ';
}

输出:

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

演示


2
这更加通用,喜欢它的处理方式。 - Const

1
不,这是不可能的,但是你可以制作一个助手来实现它:
#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<<' ';
    }
}

输出

2 4 1 3 5 7 9 6 

相似的想法,不同的方法 ;) - Pepijn Kramer
@PepijnKramer 是的 :) 由于使用了 push_back,你的可能更优化,我希望编译器能够优化我的单元素数组。 - Quimby
这道题目对于练习C++17的模板元编程来说很有趣。而且我不认为有人会注意到多余的单元素数组。解决方案仍然是O(n) :) - Pepijn Kramer
@PepijnKramer 我最喜欢的问题类型 :) 绝对的,在我的屏幕上,您必须向右滚动才能看到数组,没有人会这样做 :D - Quimby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接