boost/filesystem/basic_path.hpp
Introduction
Grammar for generic path strings
Normalized Form
Header synopsis
Class basic_path
System-specific path representation
Representation example
Caution for POSIX and UNIX
programmers
Good programming practice:
relative paths
Path equality vs path equivalence
User-defined path example
Member functions
Non-member functions
Rationale
Path decomposition examples
Boost.Filesystem traffics in objects of class template basic_path, provided by this header. The header also declares functions for error checking and class template basic_filesystem_error.
For actual operations on files and directories, see boost/filesystem/operations.hpp documentation.
For file I/O stream operations, see boost/filesystem/fstream.hpp documentation.
The Filesystem Library's Common Specifications apply to all member and non-member functions supplied by this header.
The Portability Guide discusses basic_path naming issues which are important when portability is a concern.
Class template basic_path provides for portable mechanism for representing paths in C++ programs, using a portable generic path string grammar. When portability is not a requirement, native file system specific formats can be used. Class basic_path is concerned with the lexical and syntactic aspects of a path. The path does not have to exist in the operating system's file system, and may contain names which are not even valid for the current operating system.
Rationale: If the library's functions trafficked in std::string or C-style strings, the functions would provide only an illusion of portability since while the syntax of function calls would be portable, the semantics of the strings they operate on would not be portable. A number of supporting functions are also neatly packaged in class basic_path.
An object of class basic_path can be conceptualized as containing a sequence
of strings. Each string is said to be an element of the path. Each element
represents the name of a directory, or, in the case
of the string representing the element farthest from the root in the directory
hierarchy, the name of a directory or file. The names ".." and
"." are reserved to represent the concepts of parent-directory
and directory-placeholder.
This conceptual path representation is independent of any particular operating system's representation of the path as a single string.
There is no requirement that an implementation of class basic_path actually contain a sequence of strings, but conceptualizing the contents as a sequence of strings provides a completely portable way to reason about paths.
So that programs can portably express paths as a single string, class basic_path defines a grammar for a portable generic path string format, and supplies constructor and append operations taking such strings as arguments.
Access functions are provided to retrieve the contents of a object of class basic_path formatted as a portable path string, a directory path string using the operating system's format, and a file path string using the operating system's format. Additional access functions retrieve specific portions of the contained path.
The grammar is as specified in [POSIX-01] 3.266 Pathname, 3.169 Filename, and 4.11 Pathname Resolution. This is the familiar left-to-right, "/" separated, list of names used by POSIX, Windows, and Internet URL's.
The grammar can be described by the following Extended BNF, with terminal symbols in quotes:
path ::= [root] [relative-path] // an empty path is validroot ::= [root-name] [root-directory] | separator separator {separator} nameroot-directory ::= separator { separator }relative-path ::= path-element { separator {separator} path-element } {separator}path-element ::= name | parent-directory | directory-placeholdername ::= char { char }directory-placeholder ::= "."parent-directory ::= ".." separator ::= "/" // an implementation may define additional alternate separators
Although it isn't used very often, [POSIX-01] specifies that multiple separators be treated as a single separator, and that is reflected in the EBNF grammar.
root-name grammar is implementation-defined for the particular operating system. For example, it is used to represent drive specifiers for the Window operating system. It is not used for POSIX implementations.
char may not be slash ('/') or '\0'. In additional, operating and file systems place additional restrictions on the characters which may appear in names. See File and Directory Name Recommendations.
Although implementation-defined, it is desirable that root-name have a grammar which is distinguishable from other grammar elements, and follow the conventions of the operating system.
The meaning, if any, of a root in the form separator separator name is operating system dependent.
An optional trailing "/" is allowed in a relative-basic_path. It has no particular meaning to class basic_path. It may or may not have meaning to operations functions, depending on the particular operating system. It will be ignored by operations functions on operating systems for which a trailing "/" is ignored or invalid.
Whether or not a generic path string is actually portable to a particular operating system or file system will depend on the names used. See the Portability Guide.
In Normalized form, adjacent name, parent-directory elements are recursively removed. Except in a single element path, directory-placeholders are removed.
Thus a non-empty path in normal form either has no directory-placeholders, or consists solely of one directory-placeholder. If it has parent-directory elements, they precede all name elements.
See Error Handling for portion of synopsis dealing with exceptions and error reporting.
namespace boost
{
namespace filesystem
{
template<class String, class Traits> class basic_path;
struct path_traits;
struct wpath_traits;
typedef basic_path< string_type, path_traits > path;
typedef basic_path< std::wstring, wpath_traits > wpath;
struct path_traits
{
typedef std::string internal_string_type;
typedef implementation-defined external_string_type;
static external_string_type to_external( const path &, const internal_string_type & src );
static internal_string_type to_internal( const external_string_type & src );
};
struct wpath_traits
{
typedef std::wstring internal_string_type;
typedef implementation-defined external_string_type;
static external_string_type to_external( const path &, const internal_string_type & src );
static internal_string_type to_internal( const external_string_type & src );
static void imbue( const std::locale & loc );
static bool imbue( const std::locale & loc, const std::nothrow_t & );
private:
static bool m_locked = false; // for exposition only
static std::locale m_locale = std::locale(""); // for exposition only
};
template<class String, class Traits>
class basic_path
{
public:
typedef basic_path<String, Traits> path_type;
typedef String string_type;
typedef Traits traits_type;
typedef typename Traits::external_string_type external_string_type;
// compiler generates copy constructor and copy assignment
// constructors:
basic_path();
basic_path( const string_type & src );
basic_path( const char * src );
~basic_path();
// append operations:
basic_path & operator /= ( const basic_path & rhs );
basic_path & operator /= ( const string_type & rhs );
basic_path & operator /= ( const typename string_type::value_type * rhs );
basic_path operator / ( const basic_path & rhs ) const;
basic_path operator / ( const string_type & rhs ) const;
basic_path operator / ( const typename string_type::value_type * rhs ) const;
// modification functions:
basic_path & normalize();
basic_path & remove_leaf();
// conversion functions:
const string_type & string() const;
const string_type file_string() const;
const string_type directory_string() const;
const external_string_type external_file_string() const;
const external_string_type external_directory_string() const;
// decomposition functions:
basic_path root_path() const;
string_type root_name() const;
string_type root_directory() const;
basic_path relative_path() const;
string_type leaf() const;
basic_path branch_path() const;
// query functions:
bool empty() const;
bool is_complete() const;
bool has_root_path() const;
bool has_root_name() const;
bool has_root_directory() const;
bool has_relative_path() const;
bool has_leaf() const;
bool has_branch_path() const;
// iteration:
typedef implementation-defined iterator;
typedef iterator const_iterator;
iterator begin() const;
iterator end() const;
// relational operators:
bool operator==( const basic_path & that ) const;
bool operator!=( const basic_path & that ) const;
bool operator<( const basic_path & that ) const;
bool operator<=( const basic_path & that ) const;
bool operator>( const basic_path & that ) const;
bool operator>=( const basic_path & that ) const;
private:
std::vector<string_type> m_name; // for exposition only
bool m_trailing_separator; // for exposition only
};
template<class String, class Traits>
basic_path<String, Traits>
operator/( const String & lhs, const basic_path<String, Traits> & rhs );
template<class String, class Traits>
basic_path<String, Traits>
operator/( const typename String::value_type * lhs, const basic_path<String, Traits> & rhs );
// name_check functions
bool portable_posix_name( const std::string & name );
bool windows_name( const std::string & name );
bool portable_name( const std::string & name );
bool portable_directory_name( const std::string & name );
bool portable_file_name( const std::string & name );
bool native( const std::string & name );
// is_basic_path trait class
template<class Path> struct is_basic_path { static constant bool value = false; };
// specializations for library supplied basic_path instances
// ( must also be specialized by user for user supplied basic_path types )
template<> struct is_basic_path<path> { static constant bool value = true; };
template<> struct is_basic_path<wpath> { static constant bool value = true; };
}
}
String template arguments shall support the same set of well-formed expressions as std::basic_string.
Traits template arguments shall support as well-formed the expressions in the following
table, where p is an object of type
basic_path<String, Traits>, s is an object of type
String, and loc is an object of type std::locale.
| Traits Requirements | |
| Expression | Type |
Traits::internal_string_type |
Implementation defined |
Traits::external_string_type |
Implementation defined |
Traits::to_internal(p, s) |
internal_string_type |
Traits::to_external(p, s) |
external_string_type |
Class path is supplied with the following traits members:
| Struct path_traits | |
| Member | Behavior |
internal_string_type |
std::string |
external_string_type |
For POSIX and Windows: std::string |
to_internal(p, s) |
Returns: s |
to_external(p, s) |
Returns: s |
For POSIX implementations, class wpath is supplied with the following traits members:
| Struct wpath_traits for POSIX | |
| Member | Behavior |
internal_string_type |
std::string |
external_string_type |
For POSIX: std::string. For Windows: std::wstring |
to_internal(p, s) |
Effects: m_locked = true;Returns: s, converted by m_locale's codecvt facet
to the internal string type and encoding. |
to_external(p, s) |
Effects: m_locked = true;Returns: s, converted by m_locale's codecvt facet
to the internal string type and encoding. |
imbue(loc) |
Throws: if m_locked == true.Effects: m_locked = true; m_locale = loc;Returns: void |
imbue(loc, std::nothrow) |
Effects: if (!m_locked) m_locale = loc; bool temp(m_locked); m_locked = true;Returns: temp |
For Windows implementations, class wpath is supplied with the following traits members:
| Struct wpath_traits for Windows | |
| Member | Behavior |
internal_string_type |
std::string |
external_string_type |
For POSIX: std::string. For Windows: std::wstring |
to_internal(p, s) |
Effects: m_locked = true;Returns: s |
to_external(p, s) |
Effects: m_locked = true;Returns: s |
imbue(loc) |
Throws: if m_locked == true.Effects: m_locked = true;Returns: void |
imbue(loc, std::nothrow) |
Effects: bool temp(m_locked); m_locked = true;Returns: temp |
For an example of a user supplied Traits class, see lpath.hpp, which is used to test user defined wide character support.
For the sake of exposition, certain member functions are described as if the class contains the indicated private members. Actual implementations may differ.
For functions string, file_string, directory_string,
external_file_string, and external_directory_string, implementations
are permitted to return either const or const &
values.
Note: There is no guarantee that a basic_path object represents a path which is considered valid by the current operating system. A path might be invalid to the operating system because it contains invalid names (too long, invalid characters, and so on), or because it is a partial path still as yet unfinished by the program. An invalid path will normally be detected at time of use, such as by one of the library's operations or fstream functions.
Portability Warning: There is no guarantee that a basic_path object represents a path which would be portable to another operating system or file system. A path might be non-portable because it contains names which the operating system considers too long or containing invalid characters. To detect portability problems, the library supplies several name_check functions.
Several basic_path member functions return representations of m_name in formats specific to the operating system. For POSIX, the system specific format is the same as the generic grammar. For Window, the system specific format is the same as the generic grammar except that the alternate separator (backslash) is always used. For other operating systems, the system specific format is implementation defined.
For operating systems other than POSIX and Windows, if an m_name
element contains characters which are invalid under the operating system's
rules, and there is an unambiguous and reasonable translation between the
invalid character and a valid character, the implementation is required to
perform that translation. For example, if an operating system does not permit
lowercase letters in file or directory names, these letters must be translated
to uppercase if unambiguous. Such translation does not apply to the representation
returned by string().
The rule-of-thumb is to use string() when a generic string representation of the basic_path is required, and use either directory_string() or file_string() when a string representation formatted for the particular operating system is required.
The difference between the representations returned by string(), directory_string(), and file_string() are illustrated by the following code:
path my_path( "foo/bar/data.txt" ); std::cout << "string------------------: " << my_path.string() << '\n' << "directory_string--------: " << my_path.directory_string() << '\n' << "file_string-------------: " << my_path.file_string() << '\n';
On POSIX systems, the output would be:
string------------------: foo/bar/data.txt directory_string--------: foo/bar/data.txt file_string-------------: foo/bar/data.txt
On Windows, the output would be:
string------------------: foo/bar/data.txt directory_string--------: foo\bar\data.txt file_string-------------: foo\bar\data.txt
On classic Mac OS, the output would be:
string------------------: foo/bar/data.txt directory_string--------: foo:bar:data.txt file_string-------------: foo:bar:data.txt
On a hypothetical operating system using OpenVMS format representations, it would be:
string------------------: foo/bar/data.txt directory_string--------: [foo.bar.data.txt] file_string-------------: [foo.bar]data.txt
Note that that because OpenVMS uses period as both a directory separator character and as a separator between filename and extension, directory_string() in the example produces a useless result. On this operating system, the programmer should only use this path as a file path. (There is also a portability recommendation to not use periods in directory names.)
POSIX and other UNIX-like operating systems have a single root, while many other operating systems support multiple roots. Multi-root operating systems require a root-name such as a drive, device, disk, volume, or network name for a basic_path to be resolved to an actual specific file or directory. Because of this, the root_path() and root_directory() functions return identical results on UNIX and other single-root operating systems, but different results on multi-root operating systems. Thus use of the wrong function will not be apparent on UNIX-like systems, but will result in non-portable code which will fail when used on multi-root systems. UNIX programmers are cautioned to use particular care in choosing between root_path() and root_directory(). If undecided, use root_path().
The same warning applies to has_root_path() and has_root_directory().
It is usually bad programming practice to hard-code complete paths into programs. Such programs tend to be fragile because they break when directory trees get reorganized or the programs are moved to other machines or operating systems.
The most robust way to deal with basic_path completion is to hard-code only relative paths. When a complete basic_path is required, it can be obtained in several ways:
create_directory( "foo" ); // operating system will complete path
path foo( initial_path<path>() / "foo" );
Are paths "abc" and "ABC" equal? No, never, when equality
is determined via
class basic_path's operator==, which considers the two path's
lexical representations only.
Do paths "abc" and "ABC" resolve to the same file or directory? The answer is "yes", "no", or "maybe" depending on the operating system and external file system. The operations function equivalent() determines if two paths resolve to the same external file system entity.
Programmers wishing to determine if two paths are "the same" must decide if "the same" means "the same representation" or "resolve to the same actual file or directory", and choose the appropriate function accordingly.
The example class mbpath provides a wide-character basic_path which uses a multi-byte character string as the external representation. This is the same behavior as wpath on POSIX, but wpath on Windows assumes wide character representation supplied by NTFS and other file systems which support wide-character filenames. Class mbpath is thus useful because it always works as intended, even on FAT or other narrow-character name file systems. It is also useful when the same program must support multiple locales.
See mbpath.hpp and mbpath.cpp. For a use example, see mbcopy.cpp.
basic_path();Effects: Default constructs an object of class basic_path.
Postcondition: basic_path().empty()
basic_path( const string_type & src ); basic_path( const char * src );Precondition:
src != 0.Effects: For each element in
src, as determined by the grammar,m_name.push_back(element). Setm_trailing_separatorto true ifsrchas a trailing separator, else false.Rationale: The single-argument constructors are not
explicitbecause an intended use is automatic conversion of strings to paths.
basic_path & operator/=( const basic_path & rhs ); basic_path & operator /=( const string_type & rhs ); basic_path & operator /=( const typename string_type::value_type * rhs );Effects: For each element in
rhs, as determined by the grammar,m_name.push_back(element). Setm_trailing_separatorto true ifrhshas a trailing separator, else false.Returns:
*this
basic_path operator/( const basic_path & rhs ) const; basic_path operator/( const string_type & rhs ) const; basic_path operator/( const typename string_type::value_type * rhs ) const;Returns:
basic_path( *this ) /= rhsRationale: Operator / is supplied because together with operator /=, it provides a convenient way for users to supply paths with a variable number of elements. For example,
initial_path<path>() / "src" / src_name. Operator+ and operator+= were considered as alternative names, but deemed too easy to confuse with those operators for std::basic_string. Operator<< and operator=<< were used until Dave Abrahams pointed out that / and /= match the generic path syntax.Note: Also see non-member operator/ functions.
basic_path & normalize();Postcondition: m_name is in normalized form.
Returns:
*this
basic_path & remove_leaf();Effects: If
has_branch_path()thenm_name.pop_back().Returns:
*thisRationale: This function is needed to implement directory iterators. It is made public so other uses can benefit from the functionality.
const string_type & string() const;Returns: The contents of
m_name, formatted as a string according to the rules of the grammar, with a separator appended ifm_trailing_separatoris true.Note: The returned string must be unambiguous according to the grammar. That means that for an operating system with root-names indistinguishable from relative-path names, names containing "/", or allowing "." or ".." as directory or file names, escapes or other mechanisms will have to be introduced into the grammar to prevent ambiguities. This has not been done yet, since no current implementations are on operating systems with any of those problems.
See: Representation example above.
const string_type file_string() const;Returns: The contents of
m_name, formatted in the system-specific representation of a file path.See: Representation example above.
const string_type directory_string() const;Returns: The contents of
m_name, formatted in the system-specific representation of a directory path.See: Representation example above.
const external_string_type external_file_string() const;Returns:
file_string(), converted to the type and encoding ofexternal_string_type.Using wpath as an example, on Linux the external string type will be string_type, and the external encoding will normally be UTF-8. On Windows the wpath internal and external string types are both std::wstring, and the encoding of both is UTF-16.
const external_string_type external_directory_string() const;Returns:
directory_string(), converted to the type and encoding ofexternal_string_type.
basic_path root_path() const;Returns:
root_name() / root_directory()Portably provides a copy of a path's full root path, if any. See Path decomposition examples.
string_type root_name() const;Returns: If
!m_name.empty() && m_name[0]is a root-name, returns m_name[0], else returns a null string.Portably provides a copy of a path's root-name, if any. See Path decomposition examples.
string_type root_directory() const;Returns: If the path contains root-directory, then a string containing "/", else
string().Portably provides a copy of a path's root-directory, if any. See Path decomposition examples.
basic_path relative_path() const;Returns: A new path containing only the relative-path portion of the source path.
Portably provides a copy of a path's relative portion, if any. See Path decomposition examples.
string_type leaf() const;Returns:
empty() ? string_type() : m_name.back()A typical use is to obtain a file or directory name without path information from a path returned by a directory_iterator. See Path decomposition examples.
basic_path branch_path() const;Returns:
m_name.size() <= 1 ? path_type("") : x, wherexis a path constructed from all the elements ofm_nameexcept the last.A typical use is to obtain the parent path for a path supplied by the user. See Path decomposition examples.
bool empty() const;Returns:
string().empty().Determines if a path string itself is empty. To determine if the file or directory identified by the path is empty, use the operations.hpp is_empty() function.
Naming rationale: C++ Standard Library containers use the empty name for the equivalent functions.
bool is_complete() const;Returns: For single-root operating systems,
has_root_directory(). For multi-root operating systems,has_root_directory() && has_root_name().Naming rationale: The alternate name, is_absolute(), causes confusion and controversy because on multi-root operating systems some people believe root_name() should participate in is_absolute(), and some don't. See the FAQ.
Note: On most operating systems, a complete path always unambiguously identifies a specific file or directory. On a few systems (classic Mac OS, for example), even a complete path may be ambiguous in unusual cases because the OS does not require unambiguousness.
bool has_root_path() const;Returns:
has_root_name() || has_root_directory()
bool has_root_name() const;Returns:
!root_name().empty()
bool has_root_directory() const;Returns:
!root_directory().empty()
bool has_relative_path() const;Returns:
!relative_path().empty()
bool has_leaf() const;Returns:
!leaf().empty()
bool has_branch_path() const;Returns:
!branch_path().empty()
typedef implementation-defined iterator;
typedef iterator const_iterator;A constant iterator meeting the C++ Standard Library requirements for bidirectional iterators (24.1). The iterator is a class type (so that operator++ and -- will work on temporaries). The value, reference, and pointer types are std::basic_string<>, const std::basic_string<> &, and const std::basic_string<> *, respectively.
Note that non-root paths ending with a slash ("foo/") are treated as if there was a trailing "." ("foo/."), per the POSIX and Windows specifications.
iterator begin() const;Returns:
m_path.begin()
iterator end() const;Returns:
m_path.end()
bool operator==( const basic_path & that ) const;Returns:
!(*this < that) && !(that < *this)
bool operator!=( const basic_path & that ) const;Returns:
!(*this == that)
bool operator<( const basic_path & that ) const;Returns:
std::lexicographical_compare( begin(), end(), that.begin(), that.end() )See Path equality vs basic_path equivalence.
Rationale: Relational operators are provided to ease uses such as specifying paths as keys in associative containers. Lexicographical comparison is used because:
- Even though not a full-fledged standard container, paths are enough like containers to merit meeting the C++ Standard Library's container comparison requirements (23.1 table 65).
- The alternative is to return
this->string() < that.string(). But basic_path::string() as currently specified can yield non-unique results for differing paths. The case (from Peter Dimov) isbasic_path("first/")/"second"andbasic_path("first")/"second"both returning a string() of"first/second". [TODO: reanalyze this. It doesn't make sense anymore Did something change?]
bool operator<=( const basic_path & that ) const;Returns:
!(that < *this)
bool operator>( const basic_path & that ) const;Returns:
that < *this
bool operator>=( const basic_path & that ) const;Returns:
!(*this < that)
template<class String, class Traits>
basic_path<String, Traits> operator/(
const typename String::value_type * lhs, const basic_path<String, Traits> & rhs );
template<class String, class Traits>
basic_path<String, Traits> operator/(
const String & lhs, const basic_path<String, Traits> & rhs );Returns:
basic_path( lhs ) /= rhs
Also see the FAQ for additional rationale.
Function naming: Class basic_path member function names and operations.hpp non-member function names were chosen to be somewhat distinct from one another. The objective was to avoid cases like foo.empty() and empty( foo ) both being valid, but with completely different semantics. At one point basic_path::empty() was renamed basic_path::is_null(), but that caused many coding typos because std::string::empty() is often used nearby.
Decomposition functions: Decomposition functions are provided because without them it is impossible to write portable basic_path manipulations. Convenience is also a factor.
Const vs non-const returns: In some earlier versions of the library, member functions returned values as const rather than non-const. See Scott Myers, Effective C++, Item 21. The const qualifiers were eliminated (1) to conform with C++ Standard Library practice, (2) because non-const returns allow occasionally useful expressions, and (3) because the number of coding errors eliminated were deemed rare. A requirement that basic_path::iterator be a class type was added to eliminate non-const iterator errors. The returns from conversion functions are still const qualified, to ensure that user programs do not break if implementations change from return by value to return by reference, as they are permitted to do.
It is often useful to extract specific elements from a basic_path object. While any decomposition can be achieved by iterating over the elements of a basic_path, convenience functions are provided which are easier to use, more efficient, and less error prone.
The first column of the table gives the example path, formatted by the string() function. The second column shows the values which would be returned by dereferencing each element iterator. The remaining columns show the results of various expressions.
| p.string() | Elements | p.root_ _path() |
p.root_ name() |
p.root_ directory() |
p.relative_ _path() |
p.root_ directory() / p.relative_ path() |
p.root_ name() / p.relative_ path() |
p.branch_ path() |
p.leaf() |
| All systems | |||||||||
/ |
/ |
/ |
"" |
/ |
"" |
/ |
"" |
"" |
/ |
foo |
foo |
"" |
"" |
"" |
foo |
foo |
foo |
"" |
foo |
/foo |
/,foo |
/ |
"" |
/ |
foo |
/foo |
foo |
/ |
foo |
foo/bar |
foo,bar |
"" |
"" |
"" |
foo/bar |
foo/bar |
foo/bar |
foo |
bar |
/foo/bar |
/,foo,bar |
/ |
"" |
/ |
foo/bar |
/foo/bar |
foo/bar |
/foo |
bar |
. |
. |
"" |
"" |
"" |
. |
. |
. |
"" |
. |
.. |
.. |
"" |
"" |
"" |
.. |
.. |
.. |
"" |
.. |
../foo |
..,foo |
"" |
"" |
"" |
../foo |
../foo |
../foo |
.. |
foo |
| Windows | |||||||||
c: |
c: |
c: |
c: |
"" |
"" |
"" |
c: |
"" |
c: |
c:/ |
c:,/ |
c:/ |
c: |
/ |
"" |
/ |
c: |
c: |
/ |
c:.. |
c:,.. |
c: |
c: |
"" |
.. |
c:.. |
c:.. |
c: |
.. |
c:foo |
c:,foo |
c: |
c: |
"" |
foo |
foo |
c:foo |
c: |
foo |
c:/foo |
c:,/,foo |
c:/ |
c: |
/ |
foo |
/foo |
c:foo |
c:/ |
foo |
//net |
//net |
//net |
//net |
"" |
"" |
"" |
//net |
"" |
//net |
//net/ |
//net,/ |
//net/ |
//net |
/ |
"" |
/ |
//net |
//net |
/ |
//net/foo |
//net, |
//net/ |
//net |
/ |
foo |
/foo |
//net/foo |
//net/ |
foo |
prn: |
prn: |
prn: |
prn: |
"" |
"" |
"" |
prn: |
"" |
prn: |
Revised 21 June, 2005
© Copyright Beman Dawes, 2002
Use, modification, and distribution are subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at www.boost.org/LICENSE_1_0.txt)