4
\$\begingroup\$

When people migrate from Python to C++, they're often bothered by the fact that C++ strings don't support multiplication like Python's strings do.

std::string does have a constructor that supports creating a string with one character repeated a specified number of times. But, if the pattern to be repeated is more than one character long, C++ provides no convenient solution.

This is an attempt at providing such a solution:

#include <string>

std::string operator*(std::string const &in, size_t m) {
    std::string ret;

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

std::string operator*(size_t m, std::string const &in) {
    std::string ret;

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

For example, using these, the typical "print a Christmas tree of asterisks" task could be written something like this:

int main() {
    using namespace std::literals;

    std::cout << "Width: ";
    int width;

    std::cin >> width;

    for (int i = 1; i < width; i+=2)
        std::cout << " "s * ((width - i-1)/2) << "*"s * i << "\n";
}

[Of course, in this particular case, we're only multiplying single-character strings, so the std::string ctor would work as well.]

Note that the trailing s to make the string literal an std::string rather than a char const * is necessary--at least one operand of a user-defined operator must be a class or enumeration (or reference to a class or enumeration).

\$\endgroup\$
5
  • \$\begingroup\$ "they're often bothered" ...Really? I don't think I've ever actually used that feature for anything in production. \$\endgroup\$
    – jpmc26
    Nov 12, 2016 at 5:01
  • 1
    \$\begingroup\$ @jpmc26: Most people try to learn the language before writing production code. \$\endgroup\$ Nov 12, 2016 at 6:00
  • \$\begingroup\$ I'm not my phone so I can't write a full answer (sorry), but as an optimization, you can do this: golang.org/src/strings/strings.go?s=10765:10804#L409 instead of the algorithm being O(N) operations it's O(log N). \$\endgroup\$ Nov 12, 2016 at 8:40
  • \$\begingroup\$ @eric_lagergren, pretty nice idea. Something around binary power, isn't it? \$\endgroup\$ Nov 12, 2016 at 10:50
  • \$\begingroup\$ Yeah, it copies bytes in chunks that double in size each iteration. Go's copy builtin function won't write past the end of the array, so no extra housekeeping is needed. \$\endgroup\$ Nov 12, 2016 at 15:08

2 Answers 2

7
\$\begingroup\$

Code duplication:

There is a code duplication in the overloads of the operator. I believe that the second should call the first with parameters swapped, or vice versa.

Last feature:

The last thing I would add, aside from what @Justin mentioned, is some support for std::basic_string<>. Although it will introduce abundance of template parameters, the code will be more or less generic.

Do note that in this case reserving might make sense.

\$\endgroup\$
5
\$\begingroup\$

You should really put the operators in a namespace. I would recommend not even using operator overloading, and instead making a repeat(std::string const& str, size_t m), but if you want to do the operator overloading, putting it in a namespace makes it easier for a user to opt-in:

#include <string>

namespace string_mul {

std::string operator*(std::string const &in, size_t m) {
    std::string ret;

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

std::string operator*(size_t m, std::string const &in) {
    std::string ret;

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

}

You could also gain a (very) slight performance benefit by pre-reserving the data for the string:

std::string operator*(std::string const &in, size_t m) {
    std::string ret;

    ret.reserve(in.size() * m + 1); // + 1 for null terminator

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

Putting it together, the code would look like so:

#include <string>

namespace string_mul {

std::string operator*(std::string const &in, size_t m) {
    std::string ret;

    ret.reserve(in.size() * m + 1);

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

std::string operator*(size_t m, std::string const &in) {
    std::string ret;

    ret.reserve(in.size() * m + 1);

    for (size_t i = 0; i < m; i++)
        ret += in;
    return ret;
}

}

// ...

int main() {
    using namespace std::literals;
    using namespace string_mul;

    std::cout << "Width: ";
    int width;

    std::cin >> width;

    for (int i = 1; i < width; i+=2)
        std::cout << " "s * ((width - i-1)/2) << "*"s * i << "\n";
}
\$\endgroup\$
4
  • \$\begingroup\$ Can I pull the code into my repo? I will add the std::basic_string<> stuff on top of it. \$\endgroup\$ Nov 11, 2016 at 18:55
  • \$\begingroup\$ @Olzhas I don't understand your question. Are you asking If you can put my recommendations in your answer? I really don't think that would be necessary, especially since you already forward the reader in to my answer \$\endgroup\$
    – Justin
    Nov 12, 2016 at 4:38
  • \$\begingroup\$ No, I just want to ask if the code in the post is public licensed, so I could take modified version into my github. \$\endgroup\$ Nov 12, 2016 at 5:38
  • \$\begingroup\$ @Olzhas Whatever I post on SE by default has their specified license (CC BY SA) attached. Usually I'm perfectly fine with anyone doing anything within the bounds of that license \$\endgroup\$
    – Justin
    Nov 12, 2016 at 10:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.