Ferenc Deak
Ferenc Deak

Reputation: 35408

Initialize a C++ (11) std::vector with the content of another one and extra elements

I need an "elegant" way of initializing a vector in the declaration phase with the content of another one and a few extra elements.

What I want to solve is:

Let's consider the following (example) declaration with initialization:

const std::vector<std::string> c90_types = {
    "char",
    "signed char",
    "unsigned char",
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "float",
    "double",
    "long double"
};

const std::vector<std::string> c99_types = {
    "char",
    "signed char",
    "unsigned char",
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "float",
    "double",
    "long double",
    "long long",
    "unsigned long long",
    "intmax_t",
    "uintmax_t"
};

as you can see c99_types has a subset which is exactly c90_types. I want to avoid the situation where I need to change the subset and then change manually the "superset" too, just to avoid the extra step that might introduce bugs :)

As a side note, I don't want to write code like:

second.insert(second.begin(), first.begin(), first.end());
second.push_back(something);

Any good and clean solutions to this?

Upvotes: 17

Views: 2631

Answers (5)

Laurent LA RIZZA
Laurent LA RIZZA

Reputation: 2965

There is a trick called "I want to initialize a const variable with something elaborate." that became possible with C++11, shamelessly stolen from Javascript.

const std::vector<std::string> c90_types = {
    "char",
    // and so on, and so forth....
};

const std::vector<std::string> c99_types = ([&](){
    const auto additional_types = { // initializer_list<const char *>, but it does not matter.
        "long long",
        "unsigned long long",
        "intmax_t",
        "uintmax_t"
    };

    std::vector<std::string> vec{c90_types};

    vec.insert(vec.end(), additional_types.begin(), additional_types.end());

    return vec;
})();

Pack your initialization logic into an unnamed lambda, and call it right away, copy-initializing your const variable.

vec is moved, not copied.

Upvotes: 18

Drax
Drax

Reputation: 13288

You can use boost::join:

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

const std::vector<std::string> c90_types = {
  "char",
  "signed char",
  "unsigned char",
  "short",
  "unsigned short",
  "int",
  "unsigned int",
  "long",
  "unsigned long",
  "float",
  "double",
  "long double"
};


auto range = boost::join(c90_types, std::vector<std::string>{
    "long long",
    "unsigned long long",
    "intmax_t",
    "uintmax_t"
});

const std::vector<std::string> c99_types(range.begin(), range.end());

Upvotes: 4

Cornstalks
Cornstalks

Reputation: 38218

Option 1: std::array

This can probably be cleaned up and improved a lot, but it's at least a starting point (it uses Jonathan Wakely's redi::index_tuple:

template<typename T, std::size_t N, unsigned... I, typename ...U>
inline auto
append_array_helper(const std::array<T, N>& array, redi::index_tuple<I...>, U&&... elements) -> std::array<T, N + sizeof...(elements)>
{
    return std::array<T, N + sizeof...(elements)>{ std::get<I>(array)..., std::forward<U>(elements)... };
}

template<typename T, std::size_t N, typename ...U>
inline auto
append_array(const std::array<T, N>& array, U&&... elements) -> std::array<T, N + sizeof...(elements)>
{
    return append_array_helper(array, typename redi::make_index_tuple<N>::type(), std::forward<U>(elements)...);
}

const std::array<std::string, 12> c90_types = {
    "char",
    "signed char",
    "unsigned char",
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "float",
    "double",
    "long double"
};

const std::array<std::string, 16> c99_types = append_array(
    c90_types,
    "long long",
    "unsigned long long",
    "intmax_t",
    "uintmax_t"
);

If you don't want to specify the array size, you can use the following method:

template<typename T, typename... U>
constexpr auto make_array(U&&... elements) -> std::array<T, sizeof...(elements)>
{
    return { std::forward<U>(elements)... };
}

const auto c90_types = make_array<std::string>(
    "char",
    "signed char",
    "unsigned char",
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "float",
    "double",
    "long double"
);

...

Option 2: Macros

Not my favorite, but it's simple and easy to understand and edit:

#define C90_TYPES         \
    "char",               \
    "signed char",        \
    "unsigned char",      \
    "short",              \
    "unsigned short",     \
    "int",                \
    "unsigned int",       \
    "long",               \
    "unsigned long",      \
    "float",              \
    "double",             \
    "long double"

#define C99_TYPES         \
    C90_TYPES,            \
    "long long",          \
    "unsigned long long", \
    "intmax_t",           \
    "uintmax_t"

const std::vector<std::string> c90_types = {
    C90_TYPES
};

const std::vector<std::string> c99_types = {
    C99_TYPES
};

Upvotes: 5

tux3
tux3

Reputation: 7320

You could define the biggest vector (here that would be c99_types) first, and then construct the others with iterators from the largest one.

Here is an example :

const vector<int> a{1,2,3,4};
const vector<int> b{begin(a), begin(a)+2}; // b is {1,2}

So you could write:

const std::vector<std::string> c99_types = {
    "char",
    "signed char",
    "unsigned char",
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "float",
    "double",
    "long double",
    "long long",
    "unsigned long long",
    "intmax_t",
    "uintmax_t"
};

const std::vector<std::string> c90_types{begin(c99_types), begin(c99_types)+12};

Upvotes: 7

Jarod42
Jarod42

Reputation: 217283

With extra code, you may still have const vector:

std::vector<std::string> make_c99_type()
{
    auto res = c90_types;
    const std::vector<std::string> extra_c99_types = {
        "long long",
        "unsigned long long",
        "intmax_t",
        "uintmax_t"
    };
    res.insert(res.end(), extra_c99_types.begin(), extra_c99_types.end());
    return res;
}

const std::vector<std::string> c99_types = make_c99_type();

Upvotes: 3

Related Questions