Why don't the std::fstream classes take a std::string?
Asked Answered
G

10

36

This isn't a design question, really, though it may seem like it. (Well, okay, it's kind of a design question). What I'm wondering is why the C++ std::fstream classes don't take a std::string in their constructor or open methods. Everyone loves code examples so:

#include <iostream>
#include <fstream>
#include <string>

int main()
{
    std::string filename = "testfile";      
    std::ifstream fin;

    fin.open(filename.c_str()); // Works just fine.
    fin.close();

    //fin.open(filename); // Error: no such method.
    //fin.close();
}

This gets me all the time when working with files. Surely the C++ library would use std::string wherever possible?

Granduncle answered 28/8, 2008 at 14:7 Comment(0)
B
26

By taking a C string the C++03 std::fstream class reduced dependency on the std::string class. In C++11, however, the std::fstream class does allow passing a std::string for its constructor parameter.

Now, you may wonder why isn't there a transparent conversion from a std:string to a C string, so a class that expects a C string could still take a std::string just like a class that expects a std::string can take a C string.

The reason is that this would cause a conversion cycle, which in turn may lead to problems. For example, suppose std::string would be convertible to a C string so that you could use std::strings with fstreams. Suppose also that C string are convertible to std::strings as is the state in the current standard. Now, consider the following:

void f(std::string str1, std::string str2);
void f(char* cstr1, char* cstr2);

void g()
{
    char* cstr = "abc";
    std::string str = "def";
    f(cstr, str);  // ERROR:  ambiguous
}

Because you can convert either way between a std::string and a C string the call to f() could resolve to either of the two f() alternatives, and is thus ambiguous. The solution is to break the conversion cycle by making one conversion direction explicit, which is what the STL chose to do with c_str().

Behave answered 1/9, 2008 at 6:53 Comment(3)
The sample shouldn't cause any ambiguity with the overload resolution rules. f(char*,std::string) is an exact match while the others would require conversion, hence the first would be the best viable function. If you removed the first f, then, as "(char*&,std::string&) -> (char*,std::string) -> (char*,char*)" is a better conversion sequence than "(char*&,std::string&) -> (char*,std::string) -> (std::string,char*)", the second f would be the best viable function. Am I missing something?Gumbotil
This micro-optimization is really idiotic, as (1) strings are tightly coupled to any construct of the language, especially with strings (this is similar to saying, I don't want GMP to depend on shorts), and (2) char* is evil, you should not encourage users to use it, if they don't have to, and millisecond in compilation time is well worth it, finally (3) they could've defined <stringfwd> and use string without depending on it, so I claim decoupling dependencies is not a valid reason at all for this case.Iceberg
The reason for an explicit cast using c_str() versus an implicit type conversion using a conversion constructor is, that a c string requires an additional \0, therefor the std::string cannot return a const char* pointer to its internal representation and there would be a performance hit. Using an explicit cast, the programmer can decide much better.Tarrah
D
14

There are several places where the C++ standard committee did not really optimize the interaction between facilities in the standard library.

std::string and its use in the library is one of these.

One other example is std::swap. Many containers have a swap member function, but no overload of std::swap is supplied. The same goes for std::sort.

I hope all these small things will be fixed in the upcoming standard.

Donee answered 28/8, 2008 at 14:18 Comment(0)
L
11

Maybe it's a consolation: all fstream's have gotten an open(string const &, ...) next to the open(char const *, ...) in the working draft of the C++0x standard. (see e.g. 27.8.1.6 for the basic_ifstream declaration)

So when it gets finalised and implemented, it won't get you anymore :)

Lehet answered 15/9, 2008 at 13:43 Comment(0)
E
9

The stream IO library has been added to the standard C++ library before the STL. In order to not break backward compatibility, it has been decided to avoid modifying the IO library when the STL was added, even if that meant some issues like the one you raise.

Ethelinda answered 16/9, 2008 at 15:31 Comment(0)
W
3

@ Bernard:
Monoliths "Unstrung." "All for one, and one for all" may work for Musketeers, but it doesn't work nearly as well for class designers. Here's an example that is not altogether exemplary, and it illustrates just how badly you can go wrong when design turns into overdesign. The example is, unfortunately, taken from a standard library near you... ~ http://www.gotw.ca/gotw/084.htm

Wiggins answered 15/9, 2008 at 13:26 Comment(0)
N
2

It is inconsequential, that is true. What do you mean by std::string's interface being large? What does large mean, in this context - lots of method calls? I'm not being facetious, I am actually interested.

It has more methods than it really needs, and its behaviour of using integral offsets rather than iterators is a bit iffy (as it's contrary to the way the rest of the library works).

The real issue I think is that the C++ library has three parts; it has the old C library, it has the STL, and it has strings-and-iostreams. Though some efforts were made to bridge the different parts (e.g. the addition of overloads to the C library, because C++ supports overloading; the addition of iterators to basic_string; the addition of the iostream iterator adaptors), there are a lot of inconsistencies when you look at the detail.

For example, basic_string includes methods that are unnecessary duplicates of standard algorithms; the various find methods, could probably be safely removed. Another example: locales use raw pointers instead of iterators.

Nelidanelie answered 28/8, 2008 at 14:39 Comment(0)
M
1

C++ grew up on smaller machines than the monsters we write code for today. Back when iostream was new many developers really cared about code size (they had to fit their entire program and data into several hundred KB). Therefore, many didn't want to pull in the "big" C++ string library. Many didn't even use the iostream library for the same reasons, code size.

We didn't have thousands of megabytes of RAM to throw around like we do today. We usually didn't have function level linking so we were at the mercy of the developer of the library to use a lot of separate object files or else pull in tons of uncalled code. All of this FUD made developers steer away from std::string.

Back then I avoided std::string too. "Too bloated", "called malloc too often", etc. Foolishly using stack-based buffers for strings, then adding all kinds of tedious code to make sure it doesn't overrun.

Moonwort answered 10/11, 2012 at 22:36 Comment(0)
L
0

Is there any class in STL that takes a string... I dont think so (couldnt find any in my quick search). So it's probably some design decision, that no class in STL should be dependent on any other STL class (that is not directly needed for functionality).

Latour answered 28/8, 2008 at 14:14 Comment(0)
N
0

I believe that this has been thought about and was done to avoid the dependency; i.e. #include <fstream> should not force one to #include <string>.

To be honest, this seems like quite an inconsequential issue. A better question would be, why is std::string's interface so large?

Nelidanelie answered 28/8, 2008 at 14:28 Comment(0)
V
0

Nowadays you can solve this problem very easily: add -std=c++11 to your CFLAGS.

Voltage answered 10/3, 2016 at 19:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.