c++ 17,是否可以参数化向量的统一初始化?
c++ 17, is it possible to parameterize uniform initialization of vector?
我有一个 2N 行的向量,其中后半部分(N 行)与前半部分基本相同,但更改了一个字符,例如:
std::vector<std::string> tests{
// First half of lines with '=' as separator between key and value
"key=value",
...
// Second half of lines with ' ' as separator between key and value
"key value",
...
};
有没有办法使用统一初始化构造来参数化分隔符(即 =
或
)以避免在初始化期间重复这些行?我想知道是否有比使用 for
循环创建它更好的方法。
从documentation读来,好像不行。
谢谢
如果每行只允许 =
那么下面的内容就可以了
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"});
}
从性能的角度来看,它并不比循环更好
因为它被标记为 c++17 并且你说字符串在编译时是已知的,所以在技术上可以通过利用可变参数函数模板并将参数解包两次来执行统一初始化,这将在编译时生成修改后的字符串。
最简单的想法是:
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)...
}};
};
其中 to_space_string
是一个 class,return 是一个类似字符串的对象,在编译时。
为此,我们需要制作一个简单的 holder,其作用类似于字符串,可在编译时转换为 std::string_view
。这是为了保证我们修改的字符串有自己独立的生命周期,不会悬空:
// 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};
}
};
那么我们所需要的只是一个函数,它接受一个编译时字符串(char
s 的数组),将它复制到 static_string<N>
对象中,然后 returns 它:
// 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;
}
最后需要的调整是将初始化列表设为 std::string
个对象的序列,我们可以使用 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))...
}};
};
有了这个,代码如下:
auto vec = make_string_view("hello=world", "goodbye=world");
将生成一个包含
的向量
hello=world
goodbye=world
hello world
goodbye world
注:
如果我们不使用 static_string
或某些等效项而是直接使用 string_view
,则字符串会悬垂。例如:
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};
}
在上面的例子中,我们return引用了临时存储storage[N]
,从而导致了悬空指针(UB)。 static_string
首先创建一个对象,其生命周期传递给调用者 (make_string_vector
),然后转换为 std::string
.
我有一个 2N 行的向量,其中后半部分(N 行)与前半部分基本相同,但更改了一个字符,例如:
std::vector<std::string> tests{
// First half of lines with '=' as separator between key and value
"key=value",
...
// Second half of lines with ' ' as separator between key and value
"key value",
...
};
有没有办法使用统一初始化构造来参数化分隔符(即 =
或
)以避免在初始化期间重复这些行?我想知道是否有比使用 for
循环创建它更好的方法。
从documentation读来,好像不行。
谢谢
如果每行只允许 =
那么下面的内容就可以了
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"});
}
从性能的角度来看,它并不比循环更好
因为它被标记为 c++17 并且你说字符串在编译时是已知的,所以在技术上可以通过利用可变参数函数模板并将参数解包两次来执行统一初始化,这将在编译时生成修改后的字符串。
最简单的想法是:
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)...
}};
};
其中 to_space_string
是一个 class,return 是一个类似字符串的对象,在编译时。
为此,我们需要制作一个简单的 holder,其作用类似于字符串,可在编译时转换为 std::string_view
。这是为了保证我们修改的字符串有自己独立的生命周期,不会悬空:
// 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};
}
};
那么我们所需要的只是一个函数,它接受一个编译时字符串(char
s 的数组),将它复制到 static_string<N>
对象中,然后 returns 它:
// 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;
}
最后需要的调整是将初始化列表设为 std::string
个对象的序列,我们可以使用 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))...
}};
};
有了这个,代码如下:
auto vec = make_string_view("hello=world", "goodbye=world");
将生成一个包含
的向量hello=world
goodbye=world
hello world
goodbye world
注:
如果我们不使用 static_string
或某些等效项而是直接使用 string_view
,则字符串会悬垂。例如:
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};
}
在上面的例子中,我们return引用了临时存储storage[N]
,从而导致了悬空指针(UB)。 static_string
首先创建一个对象,其生命周期传递给调用者 (make_string_vector
),然后转换为 std::string
.