122

This is my sample code:

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

class MyClass
{
    string figName;
public:
    MyClass(const string& s)
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

ostream& operator<<(ostream& ausgabe, const MyClass& f)
{
    ausgabe << f.getName();
    return ausgabe;
}

int main()
{
    MyClass f1("Hello");
    cout << f1;
    return 0;
}

If I comment out #include <string> I don't get any compiler error, I guess because it's kind of included through #include <iostream>. If I "right-click --> Go to Definition" in Microsoft VS they both point to the same line in the xstring file:

typedef basic_string<char, char_traits<char>, allocator<char> >
    string;

But when I run my program, I get an exception error:

0x77846B6E (ntdll.dll) in OperatorString.exe: 0xC00000FD: Stack overflow (Parameter: 0x00000001, 0x01202FC4)

Any idea why I get a runtime error when commenting out #include <string>? I'm using VS 2013 Express.

cbuchart
  • 10,847
  • 9
  • 53
  • 93
airborne
  • 3,664
  • 4
  • 15
  • 27
  • 4
    With god's grace. working perfectly on gcc , see https://ideone.com/YCf4OI – v78 May 02 '17 at 08:50
  • did you try visual studio with visual c++ and comment out include? – airborne May 02 '17 at 08:54
  • 1
    @cbuchart: Although the question was already answered, I think this is a complex enough topic that having a second answer in different words is valuable. I have voted to undelete your great answer. – Lightness Races in Orbit May 02 '17 at 09:41
  • I have to agree, I dont see why when you call operator << with ostream& ausgabe that it results in a infinite recursion. – Chad May 02 '17 at 10:19
  • @v78: In C++, it's unspecified whether headers may include other headers. Missing a header may be harmless when the header is indirectly included. – MSalters May 02 '17 at 11:44
  • @MSalters but they aren't allowed to "partially" include other headers, are they? – Ruslan May 02 '17 at 14:03
  • 5
    @Ruslan: Effectively, they are. That is to say, `#include` and `` might both include ``. – MSalters May 02 '17 at 14:21
  • 3
    In Visual Studio 2015, you get warning `...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflow` with running this line `cl /EHsc main.cpp /Fetest.exe` – CroCo May 02 '17 at 19:57
  • @CroCo same in VS 2010 starting with `/W1` – cbuchart May 03 '17 at 08:41

2 Answers2

161

Indeed, very interesting behavior.

Any idea why I get I runtime error when commenting out #include <string>

With MS VC++ compiler the error happens because if you do not #include <string> you won't have operator<< defined for std::string.

When the compiler tries to compile ausgabe << f.getName(); it looks for an operator<< defined for std::string. Since it was not defined, the compiler looks for alternatives. There is an operator<< defined for MyClass and the compiler tries to use it, and to use it it has to convert std::string to MyClass and this is exactly what happens because MyClass has a non-explicit constructor! So, the compiler ends up creating a new instance of your MyClass and tries to stream it again to your output stream. This results in an endless recursion:

 start:
     operator<<(MyClass) -> 
         MyClass::MyClass(MyClass::getName()) -> 
             operator<<(MyClass) -> ... goto start;

To avoid the error you need to #include <string> to make sure that there is an operator<< defined for std::string. Also you should make your MyClass constructor explicit to avoid this kind of unexpected conversion. Rule of wisdom: make constructors explicit if they take only one argument to avoid implicit conversion:

class MyClass
{
    string figName;
public:
    explicit MyClass(const string& s) // <<-- avoid implicit conversion
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

It looks like operator<< for std::string gets defined only when <string> is included (with the MS compiler) and for that reason everything compiles, however you get somewhat unexpected behavior as operator<< is getting called recursively for MyClass instead of calling operator<< for std::string.

Does that mean that through #include <iostream> string is only included partly?

No, string is fully included, otherwise you wouldn't be able to use it.

Pavel P
  • 15,789
  • 11
  • 79
  • 128
  • 1
    @airborne if this is the issue, when you run it in the debugger and look at the stack trace, it should be really large and constantly go back and forth between the two. – xaxxon May 02 '17 at 09:01
  • 2
    @xaxxon Most likely you won't see that, as the ctor would finish and you'll simply get stacktrace that looks as if `operator<<` endlessly calling itself. – Pavel P May 02 '17 at 09:04
  • 1
    @Thanks Pavel, you are right! When I right-click--> Go to definition on the << operator in ausgabe< string is only included partly? And is it a Visual C++ specific problem, because I just tried it with gcc and it worked – airborne May 02 '17 at 09:06
  • 1
    @airborne No, string is fully included, otherwise you wouldn't be able to use it. `operator<<` is simply missing – Pavel P May 02 '17 at 09:12
  • 19
    @airborne - It is not a "Visual C++ specific problem", but what can happen when you don't include the proper header. When using `std::string` without an `#include` all kinds of things can happen, not limited to a compile time error. Calling the wrong function or operator is apparently another option. – Bo Persson May 02 '17 at 09:21
  • 15
    Well, this isn't "calling the wrong function or operator"; the compiler is doing exactly what you told it to do. You just didn't know you were telling it to do this ;) – Lightness Races in Orbit May 02 '17 at 09:40
  • 3
    @BoPersson I'd call that VC++ problem, as they should have taken greater care to ensure that std::string doesn't exist if `` wasn't included. Basically, they could have included internally whatever they want, but actual string header could inject basic_string and all typedefs into std namespace (and without it std::string wouldn't be usable). – Pavel P May 02 '17 at 09:48
  • 18
    Using a type without including its corresponding header file is a bug. Period. Could the implementation have made the bug easier to spot? Sure. But that's not a *"problem"* with the implementation, it's a problem with the code you've written. – Cody Gray - on strike May 02 '17 at 10:22
  • 4
    Standard libraries are free to include tokens that are defined elsewhere in std within themselves, and are not required to include the entire header if they define one token. – Yakk - Adam Nevraumont May 02 '17 at 17:28
  • 3
    @BoPersson On the contrary, I would say "lulls inexperienced programmers into a false sense of security by providing `std::string`, but none of the helpers specified in ``" is very much an MSVC-specific problem, given that most compilers would either provide all of `` or hide `std::string` when `` is included. – Justin Time - Reinstate Monica May 02 '17 at 18:55
  • 4
    The problem isn't in `` making `std::string` visible (which, if we're being technical, can be seen as indirectly _required_ by the standard, due to `istream` and `ostream` indirectly deriving from `ios_base`, which defines a member type, `ios_base::failure`, that has a constructor that takes a `const string&` (as specified in [`[ios::failure]`](https://timsong-cpp.github.io/cppwp/n4140/ios::failure))), but in MSVC failing to make the relevant helpers visible as well (while they're allowed to refrain from doing so, they really shouldn't). – Justin Time - Reinstate Monica May 02 '17 at 18:56
  • 1
    I personally believe they should've either used some compiler trickery to allow `ios_base::failure` (along with any other members that may require it) to use `std::string` without making the definition externally visible, or just included `` directly, rather than enabling mistakes like this. – Justin Time - Reinstate Monica May 02 '17 at 18:59
  • 1
    At the very least, they should've been polite enough to make the header emit a warning if `std::string` is used by the user when `` is included but `` isn't, but that would require more effort on their part; they could've had `` enable a disabled-by-default Level 1 warning if ``'s header guard isn't defined (likely at the very end of the header), and had `` disable that warning if it's been enabled. [This warning would need to remain disabled when inapplicable even if `/Wall` is passed, which would require extra work on their part.] – Justin Time - Reinstate Monica May 02 '17 at 19:04
  • 1
    @CodyGray To be fair, this is an understandable bug (`` indirectly requires `std::string`, as mentioned in my third comment above this one, so they would have to go out of their way not to provide `std::string` when `` is included), and the "problem" is that the implementation does _just_ enough to lure newbies into a false sense of security, without doing anything to actually let them know about their bug. – Justin Time - Reinstate Monica May 02 '17 at 19:08
  • @Pavel, "*I'd call that VC++ problem, as they should have taken greater care to ensure that std::string doesn't exist*" I think they did by providing this warning `warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflow` – CroCo May 02 '17 at 19:58
  • 1
    @CroCo I don't think that they did it. It just happens in this case that the code results in infinite recursion and produces warning. The issue is that unintended code runs without the header. IMO real bug is that ctor isn't explicit and makes compiler do something that wasn't intended. – Pavel P May 02 '17 at 20:48
  • 2
    The fault is ultimately on the user but it's because of MS VC++ that the bug can exist. That they _can_ leave things half-defined doesn't mean they _should_. – n.caillou May 03 '17 at 05:40
  • 5
    It's somewhat humorous to see a bunch of C++ programmers arguing that the compiler and/or standard library should be doing more work to help them out. The implementation is well within its rights here, according to the standard, as has been pointed out numerous times. Could "trickery" be used to make this more obvious for the programmer? Sure, but we could also write code in Java and avoid this problem altogether. Why should MSVC make its internal helpers visible? Why should a header drag in a bunch of dependencies that it doesn't actually need? That violates the whole spirit of the language! – Cody Gray - on strike May 03 '17 at 08:06
  • @CodyGray Not to mention that, say, streams in the Standard Library are divided into like 20 different headers, presumably to help with compile times and executable sizes, and nobody seems to be much bothered by it. – Joker_vD May 03 '17 at 11:24
  • @CodyGray True. I just don't like that their implementation basically sets a newbie trap, it feels a bit like malicious compliance to me. – Justin Time - Reinstate Monica May 03 '17 at 19:20
  • 1
    @JustinTime C++ is a gigantic newbie trap. If you don't like it, use another language. – Hong Ooi May 04 '17 at 02:27
  • @HongOoi I don't like it, but I may find it necessary to work in C++ for any number of reasons (it's the language used at my workplace, or there's a library I need to use that's only in C++, etc). More importantly, ultimately everyone (not just developers!) suffers from buggy software, and tools that make bugs easier to write and harder to avoid make buggy software more common. – Kyle Strand May 10 '17 at 06:47
35

The problem is that your code is doing an infinite recursion. The streaming operator for std::string (std::ostream& operator<<(std::ostream&, const std::string&)) is declared in <string> header file, although std::string itself is declared in other header file (included by both <iostream> and <string>).

When you don't include <string> the compiler tries to find a way to compile ausgabe << f.getName();.

It happens that you have defined both a streaming operator for MyClass and a constructor that admits a std::string, so the compiler uses it (through implicit construction), creating a recursive call.

If you declare explicit your constructor (explicit MyClass(const std::string& s)) then your code won't compile anymore, since there is no way to call the streaming operator with std::string, and you'll be forced to include the <string> header.

EDIT

My test environment is VS 2010, and starting at warning level 1 (/W1) it warns you about the problem:

warning C4717: 'operator<<' : recursive on all control paths, function will cause runtime stack overflow

Community
  • 1
  • 1
cbuchart
  • 10,847
  • 9
  • 53
  • 93