没有格式说明符的 C++ 中类似 printf 的实用程序?
printf like utility in c++ without format specifier?
我正在尝试编写一个可以将其参数转换为字符串的函数。但是,我发现很难解压参数包。
这是我编写的代码:
#include <iostream>
#include <sstream>
template <typename... T>
std::string StringFormatter(T... values)
{
std::ostringstream out;
for (auto&& x : { values... }) {
out << x;
}
return out.str();
}
int main()
{
auto&& i = StringFormatter("One ", "two"); //Success
auto&& j = StringFormatter("one ", 1, "two", 2.0); //Fails
std::cout << i;
}
我知道上面的代码失败了,因为初始化列表只接受单一类型的参数。
我已经尝试过递归的方法来实现上面的实现,但是没有成功。
如果你能提出一个更好的方法来实现这一点,那将是一个很大的帮助。
你可以用 C++17 实现这个 fold expression:
template <typename... T>
std::string StringFormatter(T... values)
{
std::ostringstream out;
(out << ... << values);
return out.str();
}
现在有更好的方法(使用折叠表达式),但如果您想使用递归方法,它可能看起来像这样:
#include <sstream>
#include <string>
#include <iostream>
template <class T>
std::string stringify(T const &t) {
std::stringstream b;
b << t;
return b.str();
}
template<typename T, typename... Args>
std::string stringify(T arg, const Args&... args) {
return stringify(arg) + stringify(args...);
}
int main() {
std::string three{" three"};
std::cout << stringify("one: ", 1, " two: ", 2, three, "\n");
return 0;
}
您应该能够将其用于基本上支持流插入的任何类型。如果您传递的参数足够多,以至于参数数量的二次时间是一个问题,1) 去看心理医生,以及 2) 在这个一般顺序上随意使用更多代码:
#include <sstream>
#include <string>
#include <iostream>
namespace detail {
template <class T>
void stringify(std::ostringstream &b, T const &t) {
b << t;
}
template<typename T, typename... Args>
void stringify(std::ostringstream &os, T arg, const Args&... args) {
stringify(os, arg);
stringify(os, args...);
}
}
template <typename ...Args>
std::string stringify(const Args &...args) {
std::ostringstream os;
detail::stringify(os, args...);
return os.str();
}
int main() {
std::string three{" three"};
std::cout << stringify("one: ", 1, " two: ", 2, three, "\n");
}
...但一定要先去看心理医生。如果你传递了足够多的论点让它变得重要,那么你显然在做一些可怕的错误。
简而言之:
如果你没有 C++17 编译器,你可以依靠 int 数组技巧:
template <typename... T>
std::string StringFormatter(T... values) {
std::ostringstream out;
int arr[] = { 0, (out << values, void(), 0)... };
return out.str();
}
在参数包为空的情况下,数组开头显然无用的 0
是必需的,因为您无法实例化大小为 0 的数组。void()
用于规避假设的 operator,
过载。
The evaluation order is guaranteed 并且编译器应该能够优化生成的二进制文件中的数组。
深度:
此技术是 C++17 之前执行折叠表达式的方法。基本上我们创建了一个 sizeof...(T) + 1
元素的数组(全为 0)。这里的问题是我们正在使用 ,
运算符的属性来 运行 我们想要对参数包的每个元素进行的操作。
让我们暂时忘记参数包和模板。
当你这样做时:
something, other_thing
假设 ,
运算符没有重载,语句计算为 other_thing
。但这并不意味着 something
被忽略。它的值只是被丢弃,取而代之的是 other_thing
。我们正在使用 属性 作为我们的小技巧。
int x = 0;
int a[] = { 0, (++x, 0) }; // a is {0, 0}, x is 1
既然您可以重载 operator,
,我们只需添加一个额外的语句来避免这种假设的重载:
(something, void(), 0)
由于 operator,
是一个二元运算符,它的重载版本不能只有一个参数。通过添加对 void
求值的语句,我们可以防止选择任何假设的重载,因此可以确保我们最终得到 0
.
最后一步是将其与我们的参数包结合起来,并对结果语句执行包扩展:
(out << values, void(), 0)...
我正在尝试编写一个可以将其参数转换为字符串的函数。但是,我发现很难解压参数包。
这是我编写的代码:
#include <iostream>
#include <sstream>
template <typename... T>
std::string StringFormatter(T... values)
{
std::ostringstream out;
for (auto&& x : { values... }) {
out << x;
}
return out.str();
}
int main()
{
auto&& i = StringFormatter("One ", "two"); //Success
auto&& j = StringFormatter("one ", 1, "two", 2.0); //Fails
std::cout << i;
}
我知道上面的代码失败了,因为初始化列表只接受单一类型的参数。
我已经尝试过递归的方法来实现上面的实现,但是没有成功。
如果你能提出一个更好的方法来实现这一点,那将是一个很大的帮助。
你可以用 C++17 实现这个 fold expression:
template <typename... T>
std::string StringFormatter(T... values)
{
std::ostringstream out;
(out << ... << values);
return out.str();
}
现在有更好的方法(使用折叠表达式),但如果您想使用递归方法,它可能看起来像这样:
#include <sstream>
#include <string>
#include <iostream>
template <class T>
std::string stringify(T const &t) {
std::stringstream b;
b << t;
return b.str();
}
template<typename T, typename... Args>
std::string stringify(T arg, const Args&... args) {
return stringify(arg) + stringify(args...);
}
int main() {
std::string three{" three"};
std::cout << stringify("one: ", 1, " two: ", 2, three, "\n");
return 0;
}
您应该能够将其用于基本上支持流插入的任何类型。如果您传递的参数足够多,以至于参数数量的二次时间是一个问题,1) 去看心理医生,以及 2) 在这个一般顺序上随意使用更多代码:
#include <sstream>
#include <string>
#include <iostream>
namespace detail {
template <class T>
void stringify(std::ostringstream &b, T const &t) {
b << t;
}
template<typename T, typename... Args>
void stringify(std::ostringstream &os, T arg, const Args&... args) {
stringify(os, arg);
stringify(os, args...);
}
}
template <typename ...Args>
std::string stringify(const Args &...args) {
std::ostringstream os;
detail::stringify(os, args...);
return os.str();
}
int main() {
std::string three{" three"};
std::cout << stringify("one: ", 1, " two: ", 2, three, "\n");
}
...但一定要先去看心理医生。如果你传递了足够多的论点让它变得重要,那么你显然在做一些可怕的错误。
简而言之:
如果你没有 C++17 编译器,你可以依靠 int 数组技巧:
template <typename... T>
std::string StringFormatter(T... values) {
std::ostringstream out;
int arr[] = { 0, (out << values, void(), 0)... };
return out.str();
}
在参数包为空的情况下,数组开头显然无用的 0
是必需的,因为您无法实例化大小为 0 的数组。void()
用于规避假设的 operator,
过载。
The evaluation order is guaranteed 并且编译器应该能够优化生成的二进制文件中的数组。
深度:
此技术是 C++17 之前执行折叠表达式的方法。基本上我们创建了一个 sizeof...(T) + 1
元素的数组(全为 0)。这里的问题是我们正在使用 ,
运算符的属性来 运行 我们想要对参数包的每个元素进行的操作。
让我们暂时忘记参数包和模板。 当你这样做时:
something, other_thing
假设 ,
运算符没有重载,语句计算为 other_thing
。但这并不意味着 something
被忽略。它的值只是被丢弃,取而代之的是 other_thing
。我们正在使用 属性 作为我们的小技巧。
int x = 0;
int a[] = { 0, (++x, 0) }; // a is {0, 0}, x is 1
既然您可以重载 operator,
,我们只需添加一个额外的语句来避免这种假设的重载:
(something, void(), 0)
由于 operator,
是一个二元运算符,它的重载版本不能只有一个参数。通过添加对 void
求值的语句,我们可以防止选择任何假设的重载,因此可以确保我们最终得到 0
.
最后一步是将其与我们的参数包结合起来,并对结果语句执行包扩展:
(out << values, void(), 0)...