1

I came across something weird today, and I was wondering if any of you here could explain what's happening...

Here's a sample:

#include <iostream>
#include <cassert>
using namespace std;

#define REQUIRE_STRING(s)           assert(s != 0)
#define REQUIRE_STRING_LEN(s, n)    assert(s != 0 || n == 0)

class String {
public:
        String(const char *str, size_t len) : __data(__construct(str, len)), __len(len) {}
        ~String() { __destroy(__data); }

        const char *toString() const {
            return const_cast<const char *>(__data);
        }

        String &toUpper() {
            REQUIRE_STRING_LEN(__data, __len);
            char *it = __data;
            while(it < __data + __len) {
                if(*it >= 'a' && *it <= 'z')
                    *it -= 32;
                ++it;
            }
            return *this;
        }

        String &toLower() {
            REQUIRE_STRING_LEN(__data, __len);
            char *it = __data;
            while(it < __data + __len) {
                if(*it >= 'A' && *it <= 'Z')
                    *it += 32;
                ++it;
            }
            return *this;
        }

private:
        char *__data;
        size_t __len;

protected:
        static char *__construct(const char *str, size_t len) {
            REQUIRE_STRING_LEN(str, len);
            char *data = new char[len];
            std::copy(str, str + len, data);
            return data;
        }

        static void __destroy(char *data) {
            REQUIRE_STRING(data);
            delete[] data;
        }
};

int main() {
    String s("Hello world!", __builtin_strlen("Hello world!"));

    cout << s.toLower().toString() << endl;
    cout << s.toUpper().toString() << endl;

    cout << s.toLower().toString() << endl << s.toUpper().toString() << endl;

    return 0;
}

Now, I had expected the output to be:

hello world!
HELLO WORLD!
hello world!
HELLO WORLD!

but instead I got this:

hello world!
HELLO WORLD!
hello world!
hello world!

I can't really understand why the second toUpper didn't have any effect.

themoondothshine
  • 2,983
  • 5
  • 24
  • 34
  • 3
    All names beginning with underscores are reserved. Your naming pattern may collide with standard library internals or compiler keywords. See http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier – Mike DeSimone Apr 22 '10 at 17:39
  • You should not be using a double underscore... that is reserved by the C++ language for the implementation (compiler, standard library, etc.). Instead, use a single underscore. You should also simply call "strlen()" instead of "__builtin_strlen". – Michael Aaron Safyan Apr 22 '10 at 17:40
  • 1
    @Mike That isn't true, I'm afraid,. If his names began with a single underscore they would be legal. However, all names that contain a double underscore are reserved, and the OP should certainly not be using them in his code. –  Apr 22 '10 at 17:42
  • Also, you may be interested in "toupper" and "tolower" in . That is far more portable than adding/subtracting 32. Also, using for(int i = 0; i< len; i++) is more readable and less prone to error, since the update rule is placed in the "for" line. – Michael Aaron Safyan Apr 22 '10 at 17:42

1 Answers1

20

This is all because of your code

cout << s.toLower().toString() << endl << s.toUpper().toString() << endl;

and how toLower and toUpper are implemented. Following code should work as expected

cout << s.toLower().toString() << endl;
cout << s.toUpper().toString() << endl;

The issue is that toLower and toUpper don't create a new object but modify existing object. And when you call several modifying methods in the same block AND pass this object somewhere as argument, behavior is undefined.

EDIT: This is similar to popular question, what would be result of

int i = 5;
i += ++i + i++;

The correct answer here is the same: undefined. You can google for "sequence points" in C++ for deeper explanation.

SergGr
  • 23,570
  • 2
  • 30
  • 51
  • 2
    I believe the term is somewhat along the lines of "consecutive changes to a value without an intervening sequence point". – sbi Apr 22 '10 at 18:16
  • Thanks @iPhone! To tell you the truth, I've been programming in C++ for a long time now, and its the first I've come across sequence points!! That's definitely the correct answer -- +1! – themoondothshine Apr 23 '10 at 04:12