Libstdc++-porting-howto

Felix Natter

Legal Notice

This document can be distributed under the FDL (www.gnu.org)

Tue Jun 5 20:07:49 2001

Revision History
Revision 0.5 Thu Jun 1 13:06:50 2000 fnatter
First docbook-version.
Revision 0.8 Sun Jul 30 20:28:40 2000 fnatter
First released version using docbook-xml + second upload to libstdc++-page.
Revision 0.9 Wed Sep 6 02:59:32 2000 fnatter
5 new sections.
Revision 0.9.1 Sat Sep 23 14:20:15 2000 fnatter
added information about why file-descriptors are not in the standard
Revision 0.9.2 Tue Jun 5 20:07:49 2001 fnatter
a fix, added hint on increased portability of C-shadow-headers, added autoconf-test HAVE_CONTAINER_AT
Revision 0.9.3 Fri Jun 29 16:15:56 2001 fnatter
changed signature of nonstandard filebuf-constructor and update the section on filebuf::attach to point to ../ext/howto.html, added link to ../21/strings/howto.html in sec-stringstream, changed <link>-tags to have content (so that these links work), replace "user-space" by "global namespace" add note about gcc 3.0 and shadow-headers add section about ostream::form and istream::scan sec-vector-at: remove hint to modify headers fix spelling error in sec-stringstream
Revision 0.9.4 Mon Nov 5 17:01:04 2001 fnatter
rewrite section 1.1.3 because of gnu.gcc.help-post by Juergen Heinzl

Abstract

Some notes on porting applications from libstdc++-2.90 (or earlier versions) to libstdc++-v3. Not speaking in terms of the GNU libstdc++ implementations, this means porting from earlier versions of the C++-Standard to ISO 14882.


Table of Contents

1. Namespace std::
1.1.1. Using namespace composition if the project uses a separate namespace
1.1.2. Defining an empty namespace std
1.1.3. Avoid to use fully qualified names (i.e. std::string)
1.1.4. How some open-source-projects deal with this
2. there is no ios::nocreate/ios::noreplace in ISO 14882
3. stream::attach(int fd) is not in the standard any more
4. The new headers
4.4.1. New headers replacing C-headers
4.4.2. <fstream> does not define std::cout, std::cin etc.
5. Iterators
6. Libc-macros (i.e. isspace from <cctype>)
7. State of streams
8. vector::at is missing (i.e. gcc 2.95.x)
9. Using std::char_traits<char>::eof()
10. Using string::clear()/string::erase()
11. GNU Extensions ostream::form and istream::scan
12. Using stringstreams
13. About...

In the following, when I say portable, I will refer to "portable among ISO 14882-implementations". On the other hand, if I say "backportable" or "conservative", I am talking about "compiles with older libstdc++-implementations".

Namespace std::

The latest C++-standard (ISO-14882) requires that the standard C++-library is defined in namespace std::. Thus, in order to use classes from the standard C++-library, you can do one of three things:

  • wrap your code in namespace std { ... } => This is not an option because only symbols from the standard c++-library are defined in namespace std::.

  • put a kind of using-declaration in your source (either using namespace std; or i.e. using std::string;) => works well for source-files, but cannot be used in header-files.

  • use a fully qualified name for each libstdc++-symbol (i.e. std::string, std::cout) => can always be used

Because there are many compilers which still use an implementation that does not have the standard C++-library in namespace std::, some care is required to support these as well.

Namespace back-portability-issues are generally not a problem with g++, because versions of g++ that do not have libstdc++ in std:: use -fno-honor-std (ignore std::, :: = std::) by default. That is, the responsibility for enabling or disabling std:: is on the user; the maintainer does not have to care about it. This probably applies to some other compilers as well.

The following sections list some possible solutions to support compilers that cannot ignore std::.

Using namespace composition if the project uses a separate namespace

Gtk-- defines most of its classes in namespace Gtk::. Thus, it was possible to adapt Gtk-- to namespace std:: by using a C++-feature called namespace composition. This is what happens if you put a using-declaration into a namespace-definition: the imported symbol(s) gets imported into the currently active namespace(s). For example:

	  namespace Gtk {
	  using std::string;
	  class Window { ... }
	  }
	
In this example, std::string gets imported into namespace Gtk::. The result is that you don't have to use std::string in this header, but still std::string does not get imported into the global namespace (::) unless the user does using namespace Gtk; (which is not recommended practice for Gtk--, so it is not a problem). Additionally, the using-declarations are wrapped in macros that are set based on autoconf-tests to either "" or i.e. using std::string; (depending on whether the system has libstdc++ in std:: or not). (ideas from <llewelly@dbritsch.dsl.xmission.com>, Karl Nelson <kenelson@ece.ucdavis.edu>)

Defining an empty namespace std

By defining an (empty) namespace std:: before using it, you avoid getting errors on systems where no part of the library is in namespace std:

	  namespace std { }
	  using namespace std;
	

Avoid to use fully qualified names (i.e. std::string)

If some compilers complain about using std::string;, and if the "hack" for gtk-- mentioned above does not work, then I see two solutions:

  • Define std:: as a macro if the compiler doesn't know about std::.

    		#ifdef OLD_COMPILER
    		#define std
    		#endif
    	      
    (thanks to Juergen Heinzl who posted this solution on gnu.gcc.help)

  • Define a macro NS_STD, which is defined to either "" or "std" based on an autoconf-test. Then you should be able to use NS_STD::string, which will evaluate to ::string ("string in the global namespace") on systems that do not put string in std::. (This is untested)

How some open-source-projects deal with this

This information was gathered around May 2000. It may not be correct by the time you read this.

Table 1. Namespace std:: in Open-Source programs

clanlib usual
pingus usual
mozilla usual
libsigc++ conservative-impl

Table 2. Notations for categories

usual mostly fully qualified names and some using-declarations (but not in headers)
none no namespace std at all
conservative-impl wrap all namespace-handling in macros to support compilers without namespace-support (no libstdc++ used in headers)

As you can see, this currently lacks an example of a project which uses libstdc++-symbols in headers in a back-portable way (except for Gtk--: see the section on the gtkmm-hack).

there is no ios::nocreate/ios::noreplace in ISO 14882

I have seen ios::nocreate being used for input-streams, most probably because the author thought it would be more correct to specify nocreate "explicitly". So you can simply leave it out for input-streams.

For output streams, "nocreate" is probably the default, unless you specify std::ios::trunc ? To be safe, you can open the file for reading, check if it has been opened, and then decide whether you want to create/replace or not. To my knowledge, even older implementations support app, ate and trunc (except for app ?).

stream::attach(int fd) is not in the standard any more

Phil Edwards <pedwards@disaster.jaj.com> writes: It was considered and rejected. Not all environments use file descriptors. Of those that do, not all of them use integers to represent them.

When using libstdc++-v3, you can use

	  #include <fstream>
	

basic_filebuf<...>::basic_filebuf<...> (file, mode, size);
__c_file_type* file;
ios_base::open_mode mode;
int size;

but the the signature of this constructor has changed often, and it might change again. For the current state of this, check the howto for extensions.

For a portable solution (among systems which use filedescriptors), you need to implement a subclass of std::streambuf (or std::basic_streambuf<..>) which opens a file given a descriptor, and then pass an instance of this to the stream-constructor. For an example of this, refer to fdstream example by Nicolai Josuttis.

The new headers

All new headers can be seen in this source-code.

The old C++-headers (iostream.h etc.) are available, but gcc generates a warning that you are using deprecated headers.

New headers replacing C-headers

You should not use the C-headers (except for system-level headers) from C++ programs. Instead, you should use a set of headers that are named by prepending 'c' and, as usual, omitting the extension (.h). For example, instead of using <math.h>, you should use <cmath>. In some cases this has the advantage that the C++-header is more standardized than the C-header (i.e. <ctime> (almost) corresponds to either <time.h> or <sys/time.h>). The standard specifies that if you include the C-style header (<math.h> in this case), the symbols will be available both in the global namespace and in namespace std:: (but libstdc++ does not yet have fully compliant headers) On the other hand, if you include only the new header (i.e. <cmath>), the symbols will only be defined in namespace std:: (and macros will be converted to inline-functions).

For more information on this, and for information on how the GNU C++ implementation might reuse ("shadow") the C library-functions, have a look at www.cantrip.org.

<fstream> does not define std::cout, std::cin etc.

In earlier versions of the standard, <fstream.h>, <ostream.h> and <istream.h> used to define cout, cin and so on. Because of the templatized iostreams in libstdc++-v3, you need to include <iostream> explicitly to define these.

Iterators

The following are not proper uses of iterators, but may be working fixes for existing uses of iterators.

  • you cannot do ostream::operator<<(iterator) to print the address of the iterator => use operator<< &*iterator instead ?

  • you cannot clear an iterator's reference (iterator = 0) => use iterator = iterator_type(); ?

  • if (iterator) won't work any more => use if (iterator != iterator_type()) ?

Libc-macros (i.e. isspace from <cctype>)

Glibc 2.0.x and 2.1.x define the <ctype.h> -functionality as macros (isspace, isalpha etc.). Libstdc++-v3 "shadows" these macros as described in the section about c-headers.

Older implementations of libstdc++ (g++-2 for egcs 1.x and g++-3 for gcc 2.95.x), however, keep these functions as macros, and so it is not back-portable to use fully qualified names. For example:

	#include <cctype>
	int main() { std::isspace('X'); }
      
will result in something like this (unless using g++-v3):
	std:: (__ctype_b[(int) ( ( 'X' ) )] & (unsigned short int)
	_ISspace )  ;
      

One solution I can think of is to test for -v3 using autoconf-macros, and define macros for each of the C-functions (maybe that is possible with one "wrapper" macro as well ?).

Another solution which would fix g++ is to tell the user to modify a header-file so that g++-2 (egcs 1.x) and g++-3 (gcc 2.95.x) define a macro which tells <ctype.h> to define functions instead of macros:

	// This keeps isalnum, et al from being propagated as macros.
	#if __linux__
	#define __NO_CTYPE 1
	#endif

	[ now include <ctype.h> ]
      

Another problem arises if you put a using namespace std; declaration at the top, and include <ctype.h>. This will result in ambiguities between the definitions in the global namespace (<ctype.h>) and the definitions in namespace std:: (<cctype>).

The solution to this problem was posted to the libstdc++-v3 mailing-list: Benjamin Kosnik <bkoz@redhat.com> writes: ‘ --enable-cshadow-headers is currently broken. As a result, shadow headers are not being searched.... ’ This is now outdated, but gcc 3.0 still does not have fully compliant "shadow headers".

State of streams

At least some older implementations don't have std::ios_base, so you should use std::ios::badbit, std::ios::failbit and std::ios::eofbit and std::ios::goodbit.

vector::at is missing (i.e. gcc 2.95.x)

One solution is to add an autoconf-test for this:

	AC_MSG_CHECKING(for container::at)
	AC_TRY_COMPILE(
	[
	#include <vector>
	#include <deque>
	#include <string>
	
	using namespace std;
	],
	[
	deque<int> test_deque(3);
	test_deque.at(2);
	vector<int> test_vector(2);
	test_vector.at(1);
	string test_string("test_string");
	test_string.at(3);
	],
	[AC_MSG_RESULT(yes)
	AC_DEFINE(HAVE_CONTAINER_AT)],
	[AC_MSG_RESULT(no)])
      
If you are using other (non-GNU) compilers it might be a good idea to check for string::at separately.

Using std::char_traits<char>::eof()

	#ifdef HAVE_CHAR_TRAITS
	#define CPP_EOF std::char_traits<char>::eof()
	#else
	#define CPP_EOF EOF
	#endif
      

Using string::clear()/string::erase()

There are two functions for deleting the contents of a string: clear and erase (the latter returns the string).

	void 
	clear() { _M_mutate(0, this->size(), 0); }
      
	basic_string& 
	erase(size_type __pos = 0, size_type __n = npos)
	{ 
	return this->replace(_M_check(__pos), _M_fold(__pos, __n),
	_M_data(), _M_data()); 
	}
      
The implementation of erase seems to be more complicated (from libstdc++-v3), but clear is not implemented in gcc 2.95.x's libstdc++, so you should use erase (which is probably faster than operator=(charT*)).

GNU Extensions ostream::form and istream::scan

These are not supported any more - use stringstreams instead.

Using stringstreams

Libstdc++-v3 provides the new i/ostringstream-classes, (<sstream>), but for compatibility with older implementations you still have to use i/ostrstream (<strstream>):

	#ifdef HAVE_SSTREAM
	#include <sstream>
	#else
	#include <strstream>
	#endif
      
  • strstream is considered to be deprecated

  • strstream is limited to char

  • with ostringstream you don't have to take care of terminating the string or freeing its memory

  • istringstream can be re-filled (clear(); str(input);)

You can then use output-stringstreams like this:

	#ifdef HAVE_SSTREAM
	std::ostringstream oss;
	#else
	std::ostrstream oss;
	#endif
	oss << "Name=" << m_name << ", number=" << m_number << std::endl;
	...
	#ifndef HAVE_SSTREAM
	oss << std::ends; // terminate the char*-string
	#endif
	// str() returns char* for ostrstream and a string for ostringstream
	// this also causes ostrstream to think that the buffer's memory
	// is yours
	m_label.set_text(oss.str());
	#ifndef HAVE_SSTREAM
	// let the ostrstream take care of freeing the memory
	oss.freeze(false);
	#endif
      

Input-stringstreams can be used similarly:

	std::string input;
	...
	#ifdef HAVE_SSTREAM
	std::istringstream iss(input);
	#else
	std::istrstream iss(input.c_str());
	#endif
	int i;
	iss >> i; 
      
One (the only?) restriction is that an istrstream cannot be re-filled:
	std::istringstream iss(numerator);
	iss >> m_num;
	// this is not possible with istrstream
	iss.clear();
	iss.str(denominator);
	iss >> m_den;
      
If you don't care about speed, you can put these conversions in a template-function:
	template <class X>
	void fromString(const string& input, X& any)
	{
	#ifdef HAVE_SSTREAM
	std::istringstream iss(input);
	#else
	std::istrstream iss(input.c_str());
	#endif
	X temp;
	iss >> temp;
	if (iss.fail())
	throw runtime_error(..)
	any = temp;
	}
      
Another example of using stringstreams is in this howto.

I have read the Josuttis book on Standard C++, so some information comes from there. Additionally, there is information in "info iostream", which covers the old implementation that gcc 2.95.x uses.

About...

Please send any experience, additions, corrections or questions to fnatter@gmx.net or for discussion to the libstdc++-v3-mailing-list.