没有类似格式说明符的c ++中的printf之类的实用工具?

时间:2018-08-02 07:25:20

标签: c++ variadic-templates variadic-functions

我正在尝试编写一个可以将其参数转换为字符串的函数。但是,我发现很难解压参数包。

这是我编写的代码:

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

我知道上面的代码失败了,因为初始化列表只接受单一类型的参数。

我已经尝试了一种递归方法来实现上述实现,但是没有运气。

如果您可以提出实现此目标的更好方法,那将是一个很大的帮助。

3 个答案:

答案 0 :(得分:4)

您可以使用C ++ 17的fold expression来实现:

template <typename... T>
std::string StringFormatter(T... values)
{
    std::ostringstream out;

    (out << ... << values);

    return out.str();
}

答案 1 :(得分:3)

现在有更好的方法(带有fold表达式),但是如果您要使用递归方法,它可能看起来像这样:

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

...但是一定要先去看心理医生。如果您传递了足够多的论据来解决问题,那么您显然在做一些可怕的错误。

答案 2 :(得分:3)

简而言之:

如果没有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的数组,因此需要在数组开始处显然没用的0void()可以用来绕开假设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)...