123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- <html>
- <head>
- <meta http-equiv="Content-Language" content="en-us">
- <meta name="GENERATOR" content="Microsoft FrontPage 5.0">
- <meta name="ProgId" content="FrontPage.Editor.Document">
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>Filesystem Relative Proposal</title>
- <link href="styles.css" rel="stylesheet">
- </head>
- <body>
- <table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
- <tr>
- <td width="277">
- <a href="../../../index.htm">
- <img src="../../../boost.png" alt="boost.png (6897 bytes)" align="middle" width="300" height="86" border="0"></a></td>
- <td align="middle">
- <font size="7">Filesystem Relative<br>
- Draft Proposal</font>
- </td>
- </tr>
- </table>
- <table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse"
- bordercolor="#111111" bgcolor="#D7EEFF" width="100%">
- <tr>
- <td><a href="index.htm">Home</a>
- <a href="tutorial.html">Tutorial</a>
- <a href="reference.html">Reference</a>
- <a href="faq.htm">FAQ</a>
- <a href="release_history.html">Releases</a>
- <a href="portability_guide.htm">Portability</a>
- <a href="v3.html">V3 Intro</a>
- <a href="v3_design.html">V3 Design</a>
- <a href="deprecated.html">Deprecated</a>
- <a href="issue_reporting.html">Bug Reports </a>
- </td>
- </table>
- <p><a href="#Introduction">
- Introduction</a><br>
- <a href="#Acknowledgement">Acknowledgement</a><br>
- <a href="#Preliminary-implementation">Preliminary implementation</a><br>
- <a href="#Requirements">Requirements</a><br>
- <a href="#Issues">Issues</a><br>
- <a href="#Design-decisions">
- Design decisions</a><br>
- <a href="#Provide-separate-relative">Provide separate lexical and
- operational <code>relative</code> functions</a><br>
- <a href="#Provide-separate-proximate">Provide
- separate lexical and operational <code>proximate</code> functions</a><br>
- <a href="#Add-lexical-functions">Add lexical functions as <code>path</code> member functions</a><br>
- <a href="#Provide-normal">Provide a non-member function
- <code>lexically_normal</code> returning a
- normal form path</a><br>
- <a href="#Provide-weakly">Provide a <code>weakly_canonical</code> operational function</a><br>
- <a href="#just-work">Resolve issues in ways that "just work" for users</a><br>
- <a href="#mismatch">Specify <code>lexical relative</code> in terms
- of <code>std::mismatch</code></a><br>
- <a href="#Specify-op-rel-weakly">Specify operational <code>relative</code> in terms of <code>
- weakly_canonical</code></a><br>
- <a href="#Specify-op-rel-lex-rel">Specify operational <code>relative</code> in terms of
- <code>lexically
- relative</code></a><br>
- <a href="#Proposed-wording">Proposed wording</a><br>
- <a href="#Define-normal-form">Define <i>normal form</i></a><br>
- <a href="#New-class-path-member-functions">New class path member functions</a><br>
- <a href="#Synopsis-path">Synopsis</a><br>
- <a href="#Specification-path">Specification</a><br>
- <a href="#operational-functions">New operational functions</a><br>
- <a href="#Synopsis-ops">Synopsis</a><br>
- <a href="#Specification-ops">Specification</a></p>
- <h2>
- <a name="Introduction">Introduction</a></h2>
- <p>There have been requests for a Filesystem library relative function for at
- least ten years.</p>
- <p>
- The requested functionality seems simple - given two paths with a common
- prefix, return the non-common suffix portion of one of the paths such that
- it is relative to the other path.</p>
- <p>
- In terms of the Filesystem library,</p>
- <blockquote>
- <pre>path p("/a/b/c");
- path base("/a/b");
- path rel = relative(p, base); // the requested function
- cout << rel << endl; // outputs "c"
- assert(absolute(rel, base) == p);</pre>
- </blockquote>
- <p>If that was all there was to it, the Filesystem library would have had a
- <code>relative</code> function years ago.</p>
- <blockquote>
- <p>Blocking issues: Clashing requirements, symlinks, directory placeholders (<i>dot</i>,
- <i>dot-dot</i>), user-expectations, corner cases.</p>
- </blockquote>
- <h3><a name="Acknowledgement">Acknowledgement</a></h3>
- <p>A paper by Jamie Allsop, <i>Additions to Filesystem supporting Relative Paths</i>,
- is what broke my mental logjam. Much of what follows is based directly on
- Jamie's analysis and proposal. The <code>weakly_canonical</code> function and
- aspects of the semantic specifications are my contributions. Mistakes, of
- course, are mine.</p>
- <h3><a name="Preliminary-implementation">Preliminary implementation</a></h3>
- <p>A preliminary implementation is available in the
- <a href="https://github.com/boostorg/filesystem/tree/feature/relative2">
- feature/relative2</a> branch of the Boost Filesystem Git repository. See
- <a href="https://github.com/boostorg/filesystem/tree/feature/relative2">
- github.com/boostorg/filesystem/tree/feature/relative2</a></p>
- <h2><a name="Requirements">Requirements</a></h2>
- <b><a name="Requirement-1">Requirement 1</a>:</b> Some uses require symlinks be followed; i.e. the path must be resolved in
- the actual file system.<p><b><a name="Requirement-2">Requirement 2</a>: </b>Some uses require symlinks not be followed; i.e. the path must not be
- resolved in the actual file system.</p>
- <b><a name="Requirement-3">Requirement 3</a>: </b>Some uses require removing redundant current directory (<i>dot</i>)
- or parent directory (<i>dot-dot</i>) placeholders.<p><b>
- <a name="Requirement-4">Requirement 4</a>: </b>Some uses do not require removing redundant current directory (<i>dot</i>)
- or parent directory (<i>dot-dot</i>) placeholders since the path is known to
- be
- already in normal form.</p>
- <h2><a name="Issues">Issues</a></h2>
- <p><b><a name="Issue-1">Issue 1</a>:</b> What happens if <code>p</code>
- and <code>base</code> are themselves relative?</p>
- <b><a name="Issue-2">Issue 2</a>:</b> What happens if there is no common prefix? Is this an error, the whole of
- <code>p</code> is relative to <code>base</code>, or something else?<p><b>
- <a name="Issue-3">Issue 3</a>:</b> What happens if <code>p</code>, <code>base</code>, or both are empty?</p>
- <b><a name="Issue-4">Issue 4</a>:</b> What happens if <code>p</code> and <code>base</code> are the same?<p>
- <b><a name="Issue-5">Issue 5</a>:</b> How is the "common prefix" determined?</p>
- <b><a name="Issue-6">Issue 6</a>:</b> What happens if portions of <code>p</code> or <code>base</code> exist but
- the entire path does not exist and yet symlinks need to be followed?<p><b>
- <a name="Issue-7">Issue 7</a>:</b> What happens when a symlink in the existing portion of a path is affected
- by a directory (<i>dot-dot</i>) placeholder in a later non-existent portion of
- the path?</p>
- <p><b><a name="Issue-8">Issue 8</a>:</b> Overly complex semantics (and thus
- specifications) in preliminary designs made reasoning about uses difficult.</p>
- <p><b><a name="Issue-9">Issue 9</a>: </b>Some uses never have redundant current directory (<i>dot</i>)
- or parent directory (<i>dot-dot</i>) placeholders, so a removal operation
- would be an unnecessary expense although otherwise harmless.</p>
- <h2>
- <a name="Design-decisions">Design decisions</a></h2>
- <h4>
- <a name="Provide-separate-relative">Provide separate</a> lexical and
- operational <code>relative</code> functions</h4>
- <p align="left">
- Resolves the conflict between <a href="#Requirement-1">requirement 1</a>
- and <a href="#Requirement-2">requirement 2</a> and ensures both
- requirements are met.</p>
- <p>
- A purely lexical function is needed by users working with directory
- hierarchies that do not actually exist.</p>
- <p>
- An operational function that queries the current file system for existence
- and follows symlinks is needed by users working with actual existing
- directory hierarchies.</p>
- <h4>
- <a name="Provide-separate-proximate">Provide separate</a> lexical and operational
- <code>proximate</code> functions</h4>
- <p>
- Although not the only possibility, a likely fallback when the relative
- functions cannot find a relative path is to return the path being made relative. As
- a convenience, the <code>proximate</code> functions do just that.</p>
- <h4>
- <a name="Add-lexical-functions">Add lexical functions as
- <code>path</code> member functions</a></h4>
- <p dir="ltr">
- The Filesystem library is unusual in that it has several functions with
- both lexical (i.e. cheap) and operational (i.e. expensive due to file
- system access) forms with differing semantics. It is important that users
- choose the form that meets their application's specific needs. The library
- has always made the distinction via the convention of lexical functions
- being members of class <code>path</code>, while operational functions are
- non-member functions. The lexical functions proposed here also use the
- name prefix <code>lexically_</code> to drive home the distinction.</p>
- <p>
- For the contrary argument, see Sutter and Alexandrescu, <i>C++ Coding Standards</i>, 44:
- "Prefer writing nonmember nonfriend functions", and Meyers, <i>Effective C++ Third Edition</i>, 23:
- "Prefer non-member non-friend functions to member functions."</p>
- <h4>
- <a name="Provide-normal">Provide</a><b> </b>a non-member function <code>
- <a href="#normal">lexically_normal</a></code> returning a
- <a href="#normal-form">normal form</a> path</h4>
- <p>
- Enables resolution of <a href="#Requirement-3">requirement 3</a> and
- <a href="#Requirement-4">requirement 4</a> in a way consistent with
- <a href="#Issue-9">issue 9</a>. Is a contributor to the resolution of
- <a href="#Issue-8">issue 8</a>.</p>
- <p>
- "Normalization" is the process of removing redundant current directory (<i>dot</i>)
- , parent
- directory (<i>dot-dot</i>), and directory separator elements.</p>
- <p>
- Normalization is a byproduct the current <code>canonical</code> function.
- But for the path returned by the
- proposed <code><a href="#weakly_canonical">weakly_canonical</a></code> function,
- only any leading canonic portion is in canonical form. So any trailing
- portion of the returned path has not been normalized.</p>
- <p>
- Jamie Allsop has proposed adding a separate normalization function returning a
- path, and I agree with him.</p>
- <p>
- Boost.filesystem has a deprecated non-const normalization function that
- modifies the path, but I agree with Jamie that a function returning a path
- is a better solution.</p>
- <h4>
- <a name="Provide-weakly">Provide</a><b> </b>a <code><a href="#weakly_canonical">weakly_canonical</a></code> operational function</h4>
- <p>
- Resolves <a href="#Issue-6">issue 6</a>, <a href="#Issue-7">issue 7</a>,
- <a href="#Issue-9">issue 9</a>, and is a contributor to the resolution of
- <a href="#Issue-8">issue 8</a>.</p>
- <p>
- The operational function
- <code>weakly_canonical(p)</code> returns a path composed of <code>
- canonical(x)/y</code>, where <code>x</code> is a path composed of the
- longest leading sequence of elements in <code>p</code> that exist, and
- <code>y</code> is a path composed of the remaining trailing non-existent elements of
- <code>p</code> if any. "<code>weakly</code>" refers to weakened existence
- requirements compared to the existing canonical function.</p>
- <ul>
- <li>Having <code>weakly_canonical</code> as a separate function, and then
- specifying the processing of operational <code>relative</code> arguments in
- terms of calls to <code>weakly_canonical</code> makes it much easier to
- specify the operational <code>relative</code> function and reason about it.
- The difficulty of reasoning about operational <code>relative</code>
- semantics before the invention of <code>weakly_canonical</code> was what led to its
- initial development.</li>
- <li>Having <code>weakly_canonical</code> as a separate function also allows
- use in other contexts.</li>
- <li>Specifying the return be in <a href="#normal-form">normal form</a> is an
- engineering trade-off to resolve <a href="#Issue-7">issue 7</a> in a way that
- just works for most use cases.</li>
- <li>Specifying normative encouragement to not perform unneeded normalization
- is a reasonable resolution for <a href="#Issue-9">issue 9</a>.</li>
- </ul>
- <h4>
- Resolve issues in ways that "<a name="just-work">just work</a>" for users</h4>
- <p>
- Resolves issues <a href="#Issue-1">1</a>, <a href="#Issue-2">2</a>,
- <a href="#Issue-3">3</a>, <a href="#Issue-4">4</a>, <a href="#Issue-7">6</a>,
- and <a href="#Issue-7">7</a>. Is a contributor to the resolution of
- <a href="#Issue-8">issue 8</a>.</p>
- <p>
- The "just works" approach was suggested by Jamie Allsop. It is implemented
- by specifying a reasonable return value for all of the "What happens
- if..." corner case issues, rather that treating them as hard errors
- requiring an exception or error code.</p>
- <h4>
- Specify <a href="#lex-proximate"><code>lexically relative</code></a> in terms
- of <code>std::<a name="mismatch">mismatch</a></code></h4>
- <p>
- Resolves <a href="#Issue-5">issue 5</a>. Is a contributor to the
- resolution of <a href="#Issue-8">issue 8</a>.</p>
- <h4>
- <a name="Specify-op-rel-weakly">Specify</a> <a href="#op-proximate">operational <code>relative</code></a> in terms of <code>
- <a href="#weakly_canonical">weakly_canonical</a></code></h4>
- <p>
- Is a contributor to the resolution of <a href="#Issue-8">issue 8</a>.</p>
- <ul>
- <li>Covers a wide range of uses cases since a single function works for
- existing, non-existing, and partially existing paths.</li>
- <li>Works correctly for partially existing paths that contain symlinks.</li>
- </ul>
- <h4>
- <a name="Specify-op-rel-lex-rel">Specify</a> <a href="#op-proximate">operational <code>relative</code></a> in terms of
- <a href="#lex-proximate"><code>lexically
- relative</code></a></h4>
- <p>
- Is a contributor to the resolution of <a href="#Issue-5">issue 5</a> and
- <a href="#Issue-8">issue 8</a>.</p>
- <p>
- If would be confusing to users and difficult to specify correctly if the
- two functions had differing semantics:</p>
- <ul>
- <li>When either or both paths are empty.</li>
- <li>When all elements of the two paths match exactly.</li>
- <li>Because different matching algorithms were used.</li>
- <li>Because although the same matching algorithm was used, it was applied in different ways.</li>
- </ul>
- <p>
- These problems are avoided by specifying operational <code>relative</code>
- in terms of lexical <code>relative</code> after preparatory
- calls to operational functions.</p>
- <h2><a name="Proposed-wording">Proposed wording</a></h2>
- <p><span style="background-color: #DBDBDB"><i>"Overview:" sections below are
- non-normative experiments attempting to make the normative reference
- specifications easier to grasp.</i></span></p>
- <h3><a name="Define-normal-form">Define <i>normal form</i></a></h3>
- <p>A path is in <b><i><a name="normal-form">normal form</a></i></b> if it has no
- redundant current directory (<i>dot</i>) or parent directory (<i>dot-dot</i>)
- elements. The normal form for an empty path is an empty path. The normal form
- for a path ending in a <i>directory-separator</i> that is not the root directory
- is the same path with a current directory (<i>dot</i>) element appended.</p>
- <p><span style="background-color: #DBDBDB"><i>The last sentence above is not
- necessary for POSIX-like or Windows-like operating systems, but supports systems
- like OpenVMS that use different syntax for directory and regular-file names.</i></span></p>
- <h3><a name="New-class-path-member-functions">New class path member functions</a></h3>
- <h4><a name="Synopsis-path">Synopsis</a></h4>
- <pre>path lexically_normal() const;
- path lexically_relative(const path& base) const;
- path lexically_proximate(const path& base) const;</pre>
- <h4><a name="Specification-path">Specification</a></h4>
- <pre>path <a name="lex-normal">lexically_normal</a>() const;</pre>
- <blockquote>
- <p><i>Overview:</i> Returns <code>*this</code> with redundant current directory
- (<i>dot</i>), parent directory (<i>dot-dot</i>), and <i>directory-separator</i> elements removed.</p>
- <p><i>Returns:</i> <code>*this</code> in <a href="#normal-form">normal form</a>.</p>
- <p><i>Remarks:</i> Uses <code>operator/=</code> to compose the returned path.</p>
- <p>[<i>Example:</i></p>
- <p><code>assert(path("foo/./bar/..").lexically_normal() == "foo");<br>
- assert(path("foo/.///bar/../").lexically_normal() == "foo/.");</code></p>
- <p>The above assertions will succeed.<i> </i>On Windows, the
- returned path's <i>directory-separator</i> characters will be backslashes rather than slashes, but that
- does not affect <code>path</code> equality.<i> —end example</i>]</p>
- </blockquote>
- <pre>path <a name="lex-relative">lexically_relative</a>(const path& base) const;</pre>
- <blockquote>
- <p><i>Overview:</i> Returns <code>*this</code> made relative to <code>base</code>.
- Treats empty or identical paths as corner cases, not errors. Does not resolve
- symlinks. Does not first normalize <code>*this</code> or <code>base</code>.</p>
- <p><i>Remarks:</i> Uses <code>std::mismatch(begin(), end(), base.begin(), base.end())</code>, to determine the first mismatched element of
- <code>*this</code> and <code>base</code>. Uses <code>operator==</code> to
- determine if elements match. </p>
- <p><i>Returns:</i> </p>
- <ul>
- <li>
- <code>path()</code> if the first mismatched element of <code>*this</code> is equal to <code>
- begin()</code> or the first mismatched element
- of <code>base</code> is equal to <code>base.begin()</code>, or<br>
- </li>
- <li>
- <code>path(".")</code> if the first mismatched element of <code>
- *this</code> is equal to <code>
- end()</code> and the first mismatched element
- of <code>base</code> is equal to <code>base.end()</code>, or<br>
- </li>
- <li>An object of class <code>path</code> composed via application of <code>
- operator/= path("..")</code> for each element in the half-open
- range [first
- mismatched element of <code>base</code>, <code>base.end()</code>), and then
- application of <code>operator/=</code> for each element in the half-open
- range
- [first mismatched element of <code>*this</code>, <code>end()</code>).
- </li>
- </ul>
- <p>[<i>Example:</i></p>
- <p><code>assert(path("/a/d").lexically_relative("/a/b/c") == "../../d");<br>
- assert(path("/a/b/c").lexically_relative("/a/d") == "../b/c");<br>
- assert(path("a/b/c").lexically_relative("a") == "b/c");<br>
- assert(path("a/b/c").lexically_relative("a/b/c/x/y") == "../..");<br>
- assert(path("a/b/c").lexically_relative("a/b/c") == ".");<br>
- assert(path("a/b").lexically_relative("c/d") == "");</code></p>
- <p>The above assertions will succeed.<i> </i>On Windows, the
- returned path's <i>directory-separator</i>s will be backslashes rather than
- forward slashes, but that
- does not affect <code>path</code> equality.<i> —end example</i>]</p>
- <p>[<i>Note:</i> If symlink following semantics are desired, use the operational function <code>
- <a href="#op-proximate">relative</a></code> <i>—end note</i>]</p>
- <p>[<i>Note:</i> If <a href="#normal">normalization</a> is needed to ensure
- consistent matching of elements, apply <code><a href="#normal">lexically_normal()</a></code>
- to <code>*this</code>, <code>base</code>, or both. <i>—end note</i>]</p>
- </blockquote>
- <pre>path <a name="lex-proximate">lexically_proximate</a>(const path& base) const;</pre>
- <blockquote>
- <p><i>Returns:</i> If the value of <code>lexically_relative(base)</code> is
- not an empty path, return it. Otherwise return <code>*this</code>.</p>
- <p>[<i>Note:</i> If symlink following semantics are desired, use the operational function
- <code><a href="#op-proximate">proximate</a></code> <i>—end note</i>]</p>
- <p>[<i>Note:</i> If <a href="#normal">normalization</a> is needed to ensure
- consistent matching of elements, apply <code><a href="#normal">lexically_normal()</a></code>
- to <code>*this</code>, <code>base</code>, or both. <i>—end note</i>]</p>
- </blockquote>
- <h3>New <a name="operational-functions">operational functions</a></h3>
- <h4><a name="Synopsis-ops">Synopsis</a></h4>
- <pre>path weakly_canonical(const path& p);
- path weakly_canonical(const path& p, system::error_code& ec);
- path relative(const path& p, system::error_code& ec);
- path relative(const path& p, const path& base=current_path());
- path relative(const path& p, const path& base, system::error_code& ec);
- path proximate(const path& p, system::error_code& ec);
- path proximate(const path& p, const path& base=current_path());
- path proximate(const path& p, const path& base, system::error_code& ec);
- </pre>
- <h4><a name="Specification-ops">Specification</a></h4>
- <pre>path <a name="weakly_canonical">weakly_canonical</a>(const path& p);
- path weakly_canonical(const path& p, system::error_code& ec);</pre>
- <blockquote>
- <i>Overview:</i> Returns <code>p</code> with symlinks resolved and the result
- normalized.<p>
- <i>Returns: </i>
- A path composed of the result of calling the <code>canonical</code> function on
- a path composed of the leading elements of <code>p</code> that exist, if any,
- followed by the elements of <code>p</code> that do not exist, if any.</p>
- <p><i>Postcondition:</i> The returned path is in <a href="#normal">normal form</a>.</p>
- <p><i>Remarks:</i> Uses <code>operator/=</code> to compose the returned path.
- Uses the <code>status</code> function to determine existence.</p>
- <p><i>Remarks:</i> Implementations are encouraged to avoid unnecessary
- normalization such as when <code>canonical</code> has already been called on the
- entirety of <code>p</code>.</p>
- <p><i>Throws:</i> As specified in Error reporting.</p>
- </blockquote>
- <pre>path <a name="op-relative">relative</a>(const path& p, system::error_code& ec);</pre>
- <blockquote>
- <p><i>Returns:</i> <code>relative(p, current_path(), ec)</code>.</p>
- <p><i>Throws:</i> As specified in Error reporting.</p>
- </blockquote>
- <pre>path relative(const path& p, const path& base=current_path());
- path relative(const path& p, const path& base, system::error_code& ec);</pre>
- <blockquote>
- <p><i>Overview:</i> Returns <code>p</code> made relative to <code>
- base</code>. Treats empty or identical paths as corner cases, not errors.
- Resolves symlinks and normalizes both <code>p</code> and <code>base</code>
- before other processing.</p>
- <p><i>Returns:</i> <code><a href="#weakly_canonical">weakly_canonical</a>(p).l<a href="#lex-proximate">exically_relative</a>(<a href="#weakly_canonical">weakly_canonical</a>(base))</code>. The second form returns <code>path()</code> if an error occurs.</p>
- <p><i>Throws:</i> As specified in Error reporting.</p>
- </blockquote>
- <pre>path <a name="op-proximate">proximate</a>(const path& p, system::error_code& ec);</pre>
- <blockquote>
- <p><i>Returns:</i> <code>proximate(p, current_path(), ec)</code>.</p>
- <p><i>Throws:</i> As specified in Error reporting.</p>
- </blockquote>
- <pre>path proximate(const path& p, const path& base=current_path());
- path proximate(const path& p, const path& base, system::error_code& ec);</pre>
- <blockquote>
- <p><i>Returns:</i> <code><a href="#weakly_canonical">weakly_canonical</a>(p).l<a href="#lex-proximate">exically_proximate</a>(<a href="#weakly_canonical">weakly_canonical</a>(base))</code>. The second form returns <code>path()</code> if an error occurs.</p>
- <p><i>Throws:</i> As specified in Error reporting.</p>
- </blockquote>
- <hr>
- <p>© Copyright Beman Dawes 2015</p>
- <p>Distributed under the Boost Software License, Version 1.0. See
- <a href="http://www.boost.org/LICENSE_1_0.txt">www.boost.org/LICENSE_1_0.txt</a></p>
- <p>Revised
- <!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B %Y" startspan -->25 October 2015<!--webbot bot="Timestamp" endspan i-checksum="32445" --></p>
- </body>
- </html>
|