how does cout << actually work?
Asked Answered
F

1

59

I was wondering how std::cout is able to use << as it does.

My main puzzlement is with whether std::cout as an instance of something. Basically, how is << defined? If I do this for a custom class, I need an instance of some sort...

I could see implementing it as kind of a hack with void pointers or something, but I'd like to see the actual way it's done.

Does anyone here know? Thanks

Farrison answered 1/4, 2011 at 3:33 Comment(39)
If you really want to see a whole lot about iostreams, you might want to take a look at a copy of amazon.com/Standard-IOStreams-Locales-Programmers-Reference/dp/…Hazy
The cout << and cin >> expressions are the reasons why C++ is so unintuitive and hard to learn.Read
@Mehrdad: If you're choosing a language because of how one writes to the console you're choosing the language for the wrong reasons. I agree the iostream library is complex, but there are plenty of awesome aspects to the language which aren't related to iostream at all (i.e. the STL). (If you don't like iostream, then don't use it. Use the bits in <cstdio> or bits specific to your platform.)Hazy
@Billy: It wasn't the console writing that I was talking about, it was the fact that a beginner needs to know operator overloading before he can even start to understand what's actually happening...Read
@Billy: Furthermore, a << b; should not have been an allowed statement; it's just like saying 2 + 3;, which -- even though valid -- makes no sense. If anything, they should've made it be a <<= b; so that it would at least give the hint to the reader that some kind of assignment is actually taking place.Read
@Mehrdad: Saying something is bad because you don't understand it is not sufficient reasoning to prove it is bad. 2 << 3 doesn't invoke operator overloading at all. As for making it illegal, you can write nonsense in any language. The language's job is not to prevent you from doing stupid things.Hazy
Err.. you edited your comment and now mine above doesn't make as much sense. But the ending point is still true. Can you do bad things with operator overloading? Sure. But just because you can do bad things with a feature doesn't mean that feature shouldn't exist.Hazy
Streams feel incredibly unintuitive, especially when it comes to formatted IO and stream manipulators. @Billy: Funny you mention that (about using C-style IO), I asked a question about doing just that and the response from C++ elitists were basically "That's wrong, you're not using C++ the way it's meant to be used".Swagman
@Billy: Operator overloading and the definition of a "statement" were separate issues I was referring to, both with their own valid uses. And just because you understand it doesn't mean it's good either... I certainly understand what's happening but I still think it's bad: just as the phrase Two plus three makes no sense as a sentence in English, the code 2 + 3; or a << b; doesn't make really make much sense as a statement either, and the fact that it's actually valid makes C++ really hard to learn (not to mention making it easier for you to shoot yourself in the foot).Read
@dreamlax: Well, hardcore C++ guys like myself are going to want you to use iostreams of course, but there are plenty of people who don't like them. C++ is not a language which forces much upon you. If you don't like the library then don't use it.Hazy
@Mehrdad: It's not a language's job to make it hard to shoot oneself in the foot. It's the language's job to cleanly express one's thoughts in a form the computer can understand. In a language where one can address memory and cause segfaults and buffer overflows and all manner of other nasty things, making hard-to-shoot-in-footness would have been (and is) an insane design goal. If you want a language with such goals then get off our lawn and go use Java or C#.Hazy
@Billy: It's not the I/O stream library that's the issue. Given what C++ has, the library is pretty well-written, actually. The problem is that the language allows for pretty much any expression to be a statement -- something that many other languages don't allow, because it honestly makes no sense to say 2 + 3;, and it just leads to the confusion of both the programmer and the reader. Had they actually forced the use of some assignment operator like cout <<= "hi"; instead of cout << "hi";, IMHO the language (and consequently the I/O library) would've been a lot better.Read
@Mehrdad: It also makes no sense to do a.Select((x) => x) (where a is an IEnumerable) in C# either, but the language lets you do it. If you want to be dumb you can be dumb anywhere. If you want "intuitive to a beginner" as a language requirement, then don't use C++.Hazy
@Billy: That's not a language issue... Select is not part of the C# language.Read
@Mehradad: Why does it matter if it is a language issue? Unintuitive is unintuitive.Hazy
It's funny how many languages have borrowed C-style IO (Ruby, Python, Shell, Go, Java, C# [to an extent]), but I can't think of one that uses C++'s style.Swagman
@Billy: Yes, you're completely right -- it's just as bad to a.Select((x) => x) in C# as it is to write a << b; in C++, because neither really makes sense as a statement. Not sure what you're trying to say, though... it just supports my point all the more. :)Read
@Mehrdad: My point is that saying "I can make feature x do nonsense!" is not justification for removal of that feature (at least in terms of general purpose programming languages). Anyone can write code in any language using most any feature which does nonsense. You can write nonsense anytime you wish. The language doesn't require that code be of high quality; that's the programmer's responsibility.Hazy
@dreamlax: Actually I agree with you here -- I'm not a fan of iostreams -- the main reason I prefer it over cstdio is that iostreams makes writing testable code easy; cstdio doesn't provide a way to make (for example) a FILE * point to a string.Hazy
@Billy: I'm not saying I can make feature x do nonsense!; I'm saying Feature x is nonsense!, where x is the ability to statements like a << b;. I'm not opposing operator overloading, nor am I saying anything about the I/O library. What I'm saying is, a << b; should be syntactically invalid as a statement for the same reason that C++ needs. is an invalid sentence in English: neither makes much sense. Had it been a <<= b;, it would've actually made sense, because = implies the side effect of an assignment, whereas << doesn't.Read
@Mehrdad: How then do you support a << b << c?Hazy
@Billy: I think you missed the point: a << b << c should be valid, but a << b << c; shouldn't. (Notice the ;.)Read
@Mehrdad: Okay, a << b << c; then. It's of the form expression;, which you can't remove because it's why (for example) a++; works.Hazy
@Billy: It's easy: you don't. (See C#, Java, D, and a whole slew of other languages that get by perfectly without it.)Read
Can you be a bit more specific? I think we can both agree that a << b needs to be a possible expression. I think we can both agree that expression; can't go, as I've already illustrated. How exactly would you resolve that issue?Hazy
@Billy: Yes, we both agree that a << b needs to be a possible expresssion. No, we do not both agree that any expression should be a valid statement; particularly, a << b; should not be a valid statement, because the << operator does not convey the fact that it is causing an assignment. And by the way, C# does not allow that.Read
But it's not causing an assignment. It's calling a function. Which the user can overload to do whatever they want.Hazy
@Billy: Take a look at this C# error when you type in 1 << 2;, or anything of the form a << b, for that matter: error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statementRead
@Billy: If you were designing a new version of English, would you let the phrase C++ is not very. be a grammatically correct sentence?Read
@Mehrdad: Okay, then we disagree. Go back to 1978 change Dennis Ritchie's mind, and then it can be changed. The language is not designed to make nonsense invalid because someone's an idiot. It's designed for someone who knows what they're doing to express ideas. C# and C and C++ have different design goals. C# has preventing nonsense as a goal. C and C++ do not.Hazy
I don't see how your English examples have anything to do with the C++ examples.Hazy
@Billy: The connection is, a + b; in C++ is like A plus B. in English. The latter doesn't make sense as a sentence, and so neither should the former as a statement... I think it follows very logically, given that humans are meant to be reading code and not computers.Read
@Mehrdad: as Billy says, a << b isn't necessarily causing an assignment. When it comes to streams, the newbie you're so keen to protect should be thinking of this is "stream b through a", so the vaguely arrow-like and directional nature of the less-than character is helpful, and for cout they don't care about buffering: they just know the data's sent and gone, so assignment would be misleading ("hey, doesn't it get stuck in cout? can't I get it back?"). And other languages do adopt C++ style: e.g. Ruby: $stdout << 99 << " red balloons"... look familiar?Needlefish
@Tony: Would you mind reading the fourth bold paragraph here? It says: Do not be clever when defining operator overloads. Operator overloading is useful in cases where it is immediately obvious what the result of the operation will be. For example, it makes sense to be able to subtract one DateTime object from another DateTime object and get a TimeSpan object. However, it is not appropriate to use the logical union operator to union two database queries, or to use the shift operator to write to a stream. (emphasis mine)Read
@Tony: ... and please don't denounce it as C#-specific. It applies to many more languages than just C#, and the reason behind the "do not be clever" -- namely human readability -- applies everywhere. There's no reason to make things more complicated than they need to be; that's where C++ failed, with things like misuse of operator overloading and the poor choice of allowing any expression whatsoever to be counted as a statement.Read
@Mehrdad: MSDN's cheapshot is exactly that. "C++ failed"? I'm sorry, but I don't accept your self-appointment as judge of that. Readability is important, and given the hands-on teaching experience Stroustrup has had I'm sure he'd have done something about << if new developers had trouble with it. You're just being melodramatic, but do feel free to use C++0x variadic macros to implement a type-safe "print(a, b, c)" if you miss BASIC. ;-PNeedlefish
@Tony: So you think MSDN just said that to promote C# against? If so, how is it a promotion -- is it because (God forbid!) it's good advice? If not, then why did it say that? Just to mislead people? Also, you're again missing the fact that I'm not referring to I/O! I'm referring to how expressions and statements are handled in general... I'm not sure why you consistently mention the I/O problem, because I'm in no way singling out iostream against any other part of C++.Read
@Tony: Try taking a look at this page, having to do with C++ itself: If you provide constructive operators, they should not change their operands. For example, x + y should not change x. (Ditto with <<.)Read
@Tony: Eh? Given the hands-on teaching experience Stroustrup has had I'm sure he'd have done something about << if new developers had trouble with it. So you think beginners have no trouble with it? For one thing, I had trouble with it when I was a beginner. For another thing, I'm not really alone; my friends ask me the same question when they're learning C++, and even others ask this same question on SO. I doubt that Stroustrup had the eye of a beginner when he designed C++, and I'm failing to see why you called this a cheapshot.Read
H
59

std::cout is an instance of std::ostream. std::cout << "something" calls one of the operator<< overloads as would be done for any instance of std::ostream.

It's "special" in that it references the console, but otherwise it behaves exactly as an ofstream or an ostringstream would.

EDIT: Chaining works just like it works for any other operators. Consider:

class MyType
{
    friend std::ostream& operator<<(std::ostream& target, const MyType& source);
    int val;
public:
    MyType()
        : val(0)
    { }
    MyType& Add(int toAdd)
    {
        val += toAdd;
        return *this;
    }
};

MyType& operator+(MyType& target, int toAdd)
{
    return target.Add(toAdd);
}

std::ostream& operator<<(std::ostream& target, const MyType& source)
{
    target << source.val;
    return target; //Make chaining work
}

int main()
{
    MyType value1;
    value1 + 2 + 3 + 4;
    std::cout << value1 << " and done!" << std::endl;
}

In this case, the chaining for +s on MyType work for the same reason the <<s work on top of std::ostream. Both + and << are left-associative, which means they're evaluated from left to right. In the case of overloaded operators, the operator is replaced with the equivalent function call.

EDIT2: In a bit more detail:

Let's say you're the compiler and you're parsing

std::cout << value1 << " and done!" << std::endl;

First, << is left associative, so you start on the left. You evaluate the first << and turn it into the function call:

operator<<(std::cout, value1) << " and done!" << std::endl;

You then see that you once again have a std::ostream (the result of the call to operator<<), and a char *, which you once again turn into the function call:

operator<<(operator<<(std::cout, value1)," and done!") << std::endl;

and so on until you've processed the entire statement.

Hazy answered 1/4, 2011 at 3:35 Comment(13)
+1 But (to play an advocate), how does chaining work? (e.g. std::cout << a << b ...)Haycock
Does it also return an std::ostream? I mean you can also do cout << "something" << "something else". How does the second operator<< operate?Widow
@pst: All of the operator<< overloads are of the form std::ostream& operator<<(std::ostream& target, TYPE& source), and they return the reference passed in as the source. That is, std::cout << a << b; is the same as operator<<(operator<<(std::cout, a), b);Hazy
cplusplus.com/reference/iostream/ostream/operator%3C%3C provides some details of what's going on.Hanschen
@pst + @highBandWidth: Added some more to the answer to explain a bit more clearly (hopefully)Hazy
cout doesn't reference the console, it refers to the operating system's default output. In some operating systems, this happens to be a console.Coquet
@MooingDuck: That's true. But I don't think going into that discussion makes sense for an answer like this aimed primarily at beginners.Hazy
C++ Primer by Prata details how and why. I read an older edition of it, though, with C++ 03 I think, or near that time.Recapture
For those who still were confused about the chaining (like I was): As we know, operations are basically nothing more than methods of classes. This means that they have a return value, which they sort of can be "replaced with" (for instance, writing 3 + 5 can be replaced with 8). In the coutand cin object's case, these return-values are always references to *this (in which case, the reference refers to the same cout/cin-object as you performed the operation on). So when you write: cout << 'a' << 'b', the first statement (cout << 'a') returns "cout" which leaves cout << 'b'Kathe
Notice: The 3 + 5-example isn't of the same concept (doesn't conclude objects), but you still get the point...Kathe
Is the << inside operator << the original << operator of it's a recursion?Greer
@MooingDuck it would basically get a handle to stdout on windows, which is whatever the process has set. SetStdHandle sets the handles for stdin/out/err in a structure ProcessParameters pointed to by the PEB. After getting the handle it will use it in a WriteFile() call, which will write to the screen buffer that the process has set (both setting a screen buffer and WriteFile to a standard stream appear to result in a IPC call to CSRSS)Amygdaline
@GuerlandoOCs: No. One is operator<<(ostream, MyType), and it calls operator<<(ostream, int). They happen to have the same name, but they're different overloads, so it's not recursion.Coquet

© 2022 - 2024 — McMap. All rights reserved.