C++的高级打印函数

3
有没有一种在C++中创建类似Python的函数或宏的方法? 我的意思是像这样的函数:
print(i); // i is an integer.
print(i, s); // i is an integer, s is a std::string. They are separated by a white space.

一个函数接收若干个参数,然后无论这些参数的类型是什么,都会将它们打印出来。


12
std::cout << i << s << std::endl 可以翻译为:输出整数变量 i 和字符串变量 s,并换行。 - sevsev
1
虽然Python技术乍一看似乎很流畅,而且<<在一开始也似乎很合理,但我的经验是这两种方法很快就会失败:试图精确控制格式非常困难和冗长。我已经爱上了老式的C风格的printf(),尽管我学会使用C++流之后很久才这样做。现在我只使用printf(),因为它能让我对字符串的显示方式进行精确而简洁的控制。 - cmaster - reinstate monica
1
@cmaster-reinstatemonica:在某些情况下,ostream的开销会成为性能瓶颈。是的,我也在许多情况下更喜欢使用printf。我更喜欢使用C++的东西来从标准输入读取数据,而使用C的东西来进行标准输出。 - Bathsheba
@cmaster-reinstatemonica Python的字符串格式化与printf非常相似,但具有类型安全性和可扩展性。 - Aykhan Hagverdili
1
@Ayxan 是的,你说得对。m 修饰符确实是 POSIX 标准的一部分。我把它和旧的 a 修饰符搞混了,后者是一个不太理想的 GNU 扩展。感谢你的纠正。 - cmaster - reinstate monica
显示剩余4条评论
3个回答

8

如果允许您使用C++17,您可以使用fold-expression实现,像这样:

#include <iostream>

template<class ...Args>
void print(const Args &...args) {
    auto seq_started = false;
    auto print_impl = [&](auto &value) mutable {
        if (seq_started) {
            std::cout << " " << value;
        } else {
            seq_started = true;
            std::cout << value;
        }
    };
    
    (print_impl(args), ...);
}

int main() {
    print("foo", 10, "bar", 20, "baz");
    return 0;
}

1
我在想为什么在使用折叠表达式时需要额外的seq_started变量。其实是不需要的,可以参考这里的解答:如何使用可变模板打印函数的参数? - JeJo

7

使用流:

std::cout << i << ' ' << s << '\n';

对于更复杂的格式,请考虑使用fmt,它支持Python的str.format语法和printf风格的格式化(但是类型安全)。


6

我理解这个问题是关于通用解决方案的,而print只是一个例子。

答案是:可以,通过重载和模板的组合。首先,你需要为每个类型设置函数:

void print_impl(const std::string& arg) {
    std::cout << arg;
}

void print_impl(int arg) {
    // Note: this is an example. Of course cout already
    // does that for us with simple
    //   std::cout << arg;
    print_impl(convert_to_string(arg));
}

// cout already has lots of overloads, we can take
// advantage of that if needed:
template<class TArg>
void print_impl(TArg&& arg) {
    std::cout << std::forward<TArg>(arg);
}

然后是模板:

template<class TArg>
void print(TArg&& arg) {
    print_impl(std::forward<TArg>(arg));
}

template<class TArg1, class ... TArgs>
void print(TArg1&& arg1, TArgs&& ... args) {
    print_impl(std::forward<TArg1>(arg1));   // <-- print first argument
    print_impl(" ");   // <-- insert separator, requires const std::string& overload
    print(std::forward<TArgs>(args)...);   // <-- recursive call
}

在上面的代码中,如果需要,可以直接用std::cout <<运算符替换对print_impl的调用。虽然我建议保留print_impl,因为它是在std::cout之上的一层良好的封装,以便将来替换它。
然后您可以像Python那样使用它:
int main()
{
    print("test");
    print(1, 2, "foo");
    print("foo", 1, 2);
    return 0;
}

出于好奇,为什么你要为intconst std::string&分别编写独立的print_impl函数?我有什么遗漏的吗? - Bathsheba
@Bathsheba 我已经更新了答案。这个特定的例子可能有些误导,因为cout已经根据类型有不同的实现。我想要展示的是你可以根据类型有不同的实现。 - freakish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接