How to write 'n' copies of a character to ostream like in python
Asked Answered
C

5

17

In python, the following instruction: print 'a'*5 would output aaaaa. How would one write something similar in C++ in conjunction with std::ostreams in order to avoid a for construct?

Clubhaul answered 10/7, 2012 at 20:43 Comment(2)
I just want to point out the rap I got on trying to introduce something like "abc"_s * 3 or std::string ("abc") * 3 to mean "abcabcabc" on this question.Tester
@chris: that's partly because people don't understand that overloading operator*(string, size_t) for string is exactly as bad (or good) as overloading operator+(string, string). No better nor worse - the meaning of multiplication by a natural number as repeated addition is fundamental mathematics, there's no excuse for misunderstanding one but not the other. It's probably also partly because your question complains about the standard and proposes a change to it (neither of which is on topic) in addition to asking a question about the motivation :-pPersephone
C
37

The obvious way would be with fill_n:

std::fill_n(std::ostream_iterator<char>(std::cout), 5, 'a');

Another possibility would be be to just construct a string:

std::cout << std::string(5, 'a');
Campanulaceous answered 10/7, 2012 at 20:48 Comment(4)
Is there a specific reason that you don't use an ostreambuf_iterator?Mar
@Columbo: None, other than not really wanting to get into explaining the difference between ostream_iterator and ostreambuf_iterator to somebody coming from Python who (in the end) probably doesn't care about the difference (though, of course, if anybody had asked, I'd probably have referred them to Xeo's answer.Campanulaceous
Perhaps surprisingly, the former results in significantly more efficient object code on both GCC and Clang. Not only can they unroll the std:fill_n operation for a stream, but, more importantly, it avoids the dynamic memory allocation required to construct (and then destroy) a string. (Why they cannot elide the dynamic memory allocation for a compile-time constant string... I'm sure there's a good reason...)Clean
@CodyGray: If you're seeing a dynamic allocation for a string of length 5 (compile time constant or otherwise), you should get a better implementation. Short string optimization has been well known for decades now. Admittedly, gcc took a long time to add it, but even it's been doing it for a fair length of time now.Campanulaceous
R
8

In C++20 you can easily write multiple copies of a character with std::format:

std::cout << std::format("{:a<5}", "");

Output:

aaaaa

If std::format is not available you can use the {fmt} library, it is based on. {fmt} also provides the print function that makes this even easier and more efficient (godbolt):

fmt::print("{:a<5}", "");

Apart from being less verbose than using ostream[_iterator] this also guarantees that the output doesn't interleave with the output of other threads, if any.

Disclaimer: I'm the author of {fmt} and C++20 std::format.

Robalo answered 16/12, 2020 at 16:6 Comment(0)
I
4

Use some tricky way: os << setw(n) << setfill(c) << ""; Where n is number of char c to write

Inmate answered 10/7, 2012 at 20:50 Comment(3)
Tricky and problematic, since the fill character doesn't reset automatically when you're done.Campanulaceous
@Jerry that is true. But it is not a serious issue since setfill(' ') after works here. BTW solution with fill_n can be broken by os << setw(5) << left << fill_n(...) - producing a aaaa :D...Inmate
You'd have to do some extra work to break the code using std::fill_n in that way. std::fill_n returns an iterator, and there's no overload of operator<< to write an iterator to a stream. You could make it compile by adding that yourself, but it doesn't seem to be something that's at all likely to happen by accident (and if you did it, your output would mostly be whatever you wrote into that overload of operator<<, not any pre-existing behavior).Campanulaceous
K
2

You can do something like that by overloading the * operator for std::string. Here is a small example

#include<iostream>
#include<string>
std::string operator*(const std::string &c,int n)
{
    std::string str;
    for(int i=0;i<n;i++)
    str+=c;
    return str;
}
int main()
{
    std::string str= "foo";
    std::cout<< str*5 <<"\n";
}
Kinata answered 10/7, 2012 at 20:50 Comment(0)
Z
1

The standard does not provide any elegant way. But one possibility (my favourite) is to use a proxy object like this

class repeat_char
{
public:
    repeat_char(char c, size_t count) : c(c), count(count) {}
    friend std::ostream & operator<<(std::ostream & os, repeat_char repeat)
    {
        while (repeat.count-- > 0)
            os << repeat.c;
        return os;
    }
private:
    char c;
    size_t count;
};

and then use it this way

std::cout << repeat_char(' ', 5);
Zoonosis answered 19/6, 2021 at 8:20 Comment(1)
can be templated template <class T> struct print_n { print_n(T v, size_t c) .... }; -- os << print_n("Ha", 3);Inmate

© 2022 - 2024 — McMap. All rights reserved.