c++ 17, is it possible to parameterize uniform initialization of vector?
Since this is tagged c++17 and you said the strings are known at compile-time, it is technically possible to perform uniform initialization by leveraging a variadic function-template and unpacking the parameters twice, and this would produce the modified string at compile-time.
The idea, in the simplest form, is to do this:
template <typename...Strings>
auto make_string_vector(const Strings&...strings) -> std::vector<std::string>
{
// Unpacks the strings twice; first unmodified, second with '=' swapped with ' '
return std::vector<std::string>{{
strings..., to_space_string(strings)...
}};
};
Where to_space_string
is a class that returns a string-like object, done at compile-time.
To do this, we need to make a simple holder that acts like a string is convertible to std::string_view
at compile time. This is to ensure that the string we modify has its own separate lifetime and does not dangle:
// A holder for the data so that we can convert it to a 'std::string' type
template <std::size_t N>
struct static_string {
char data[N];
constexpr operator std::string_view() const noexcept {
return std::string_view{data, N};
}
};
Then all we need is the function that takes a compile-time string (array of char
s), copies it into the static_string<N>
object, and returns it:
// std::string_view used so that we can do this constexpr
template <std::size_t N>
constexpr auto to_space_string(const char(&string)[N]) -> static_string<N>
{
auto storage = static_string<N>{};
std::transform(&string[0], &string[N], &storage.data[0], [](char c){
if (c == '=') { return ' '; }
return c;
});
return storage;
}
The last needed tweak would be for the initializer list to be a sequence of std::string
objects, which we can do with static_cast
s:
template <typename...Strings>
auto make_string_vector(const Strings&...strings) -> std::vector<std::string>
{
return std::vector<std::string>{{
static_cast<std::string>(strings)...,
static_cast<std::string>(to_space_string(strings))...
}};
};
With this, code like:
auto vec = make_string_view("hello=world", "goodbye=world");
will produce a vector containing
hello=world
goodbye=world
hello world
goodbye world
Live Example
Note:
If we didn't use a static_string
or some equivalent and instead used string_view
directly, the string would dangle. For example:
template <std::size_t N>
constexpr auto to_space_string(const char(&string)[N]) -> std::string_view
{
char storage[N];
std::transform(&string[0], &string[N], &storage[0], [](char c){
if (c == '=') { return ' '; }
return c;
});
// dangles after being returned!
return std::string_view{&storage[0], N};
}
In the above case, we return a reference to temporary storage storage[N]
, thus causing a dangling pointer (UB). The static_string
creates an object first whose lifetime is passed into the caller (make_string_vector
) and then gets converted to a std::string
.
If only one =
per line is allowed then following can work
std::vector<std::string> init_from_key_eq_val(std::initializer_list<const char *> strings)
{
std::vector<std::string> result;
result.reserve(2 * strings.size());
copy(strings.begin(), strings.end(), back_inserter(result));
transform(strings.begin(), strings.end(), back_inserter(result),
[](const char* str)
{
std::string str_copy(str);
size_t eq_pos = str_copy.find('=');
str_copy[eq_pos] = ' ';
return str_copy;
});
return result;
}
int main()
{
std::vector<std::string> vec = init_from_key_eq_val({"key=value"});
}
From the performance point of view, it is no better than doing it with a loop though