3

Is there a way to create a function or a macro in C++ that you can use like Python? What i mean is a function like:

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.

A function that takes in a number of arguments, then just prints them out regardless of the types.

Toàn Nguyễn
  • 111
  • 1
  • 6
  • 12
    `std::cout << i << s << std::endl` – sevsev Aug 10 '20 at 09:47
  • 1
    While the python technique seems slick on first sight, and `<<` seems sensible at first as well, it is my experience that both methods quickly fail: It's hard and/or very verbose to try to control the formatting precisely. I've come to love the old C-style `printf()` *long after* I learned to use C++ streams. And now I'm using `printf()` exclusively. Because `printf()` gives me precise, concise control over how the string appears to the user. – cmaster - reinstate monica Aug 10 '20 at 10:04
  • 1
    @cmaster-reinstatemonica: In some cases too, the `ostream` overheads are a performance bottleneck. Yes, I too prefer `printf` in many situations. I prefer the C++ stuff for reading from standard input, and C stuff for standard output. – Bathsheba Aug 10 '20 at 10:05
  • @cmaster-reinstatemonica Python sting formatting is a lot like `printf` plus type-safety and extensibility. – Aykhan Hagverdili Aug 10 '20 at 10:12
  • @Bathsheba I too prefer C-printf family output functions. The main problem is that they can't be easily extended for custom types like overloading `operator<<`. I've heard that some implementations allow you to [register](https://www.gnu.org/software/libc/manual/html_node/Customizing-Printf.html) a new `printf` function, but that's a bit too much work in my opinion. – Aykhan Hagverdili Aug 10 '20 at 10:16
  • @Bathsheba Oh, `scanf()` can be quite useful as well. If you are using the glibc, that is. Because the glibc provides the `"m"` modifier that will allocate a memory buffer to fit the input into a `"%ms"` or `"%m["` conversion. This makes string input safe. And then there's the feature that `scanf()` will return exactly the number of conversions it has performed successfully. That's invaluable for doing concise input and precise error handling at the same time. That's the way I see it, though I don't have much opportunity to hone my input parsing skills. – cmaster - reinstate monica Aug 10 '20 at 10:17
  • @cmaster-reinstatemonica I think the `m` modifier is a POSIX thing. – Aykhan Hagverdili Aug 10 '20 at 10:19
  • @Ayxan Yes, I fully agree that that's a pitfall of `printf()`. I usually solve it by providing a per-object print function that takes a `FILE*` argument. Together with `open_memstream()` that allows output to both files and strings. But it cannot extend the `printf()` syntax, which is a pity. – cmaster - reinstate monica Aug 10 '20 at 10:22
  • 1
    @Ayxan Yes, you are right. The `m` modifier is indeed part of the POSIX standard. I confused it with the older `a` modifier which was a suboptimal GNU extension. Thanks for the correction. – cmaster - reinstate monica Aug 10 '20 at 10:26

3 Answers3

8

If you are allowed to use C++17, you can do it using fold-expression, like this:

#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
    I was wondering why you need to do extra `seq_started` variable there when you use a fold-expression. You do not need that, see here [How do I print out the arguments of a function using a variadic template?](https://stackoverflow.com/questions/12342633/how-do-i-print-out-the-arguments-of-a-function-using-a-variadic-template/63434791#63434791) – JeJo Aug 16 '20 at 08:42
7

Use streams:

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

For more complex formatting consider fmt, it supports the syntax for python's str.format as well as printf style formatting (but typesafe).

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Object object
  • 1,939
  • 10
  • 19
6

I assume that the question is about general solution and that print is just an example.

The answer is: yes, with a combination of overloads and templates. First you setup functions per type:

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

then templates:

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
}

In the code above you can replace calls to print_impl with calls to std::cout << operator directly, if needed. Although I would keep print_impl because it is a nice layer above std::cout in case you want to replace it in the future.

Then you can use it similar to Python:

int main()
{
    print("test");
    print(1, 2, "foo");
    print("foo", 1, 2);
    return 0;
}
freakish
  • 54,167
  • 9
  • 132
  • 169
  • Out of interest, why do you have the separate `print_impl` functions for the `int` and the `const std::string&`? What am I missing? – Bathsheba Aug 10 '20 at 10:07
  • @Bathsheba I've updated the answer. This particular example was perhaps misleading because `cout` already has different implementations based on type. I wanted to show that you can have different implementations based on type. – freakish Aug 10 '20 at 10:08