mirror of
https://github.com/boostorg/variant.git
synced 2026-01-27 19:32:15 +00:00
521 lines
22 KiB
HTML
521 lines
22 KiB
HTML
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Language" content="en-us">
|
||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||
<meta name="keywords" content="Variant, design pattern, generic programming, C++">
|
||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||
<title>Boost::variant</title>
|
||
</head>
|
||
<body bgcolor="#FFFFFF" link="#0000FF" vlink="#800080">
|
||
<table summary="header" border="0" cellpadding="7" cellspacing="0" width="100%">
|
||
<tr>
|
||
<td valign="top" width="300">
|
||
<h3><a HREF="../../../index.htm"> <img src="../../../c++boost.gif" alt="C++ Boost" width="277" height="86" BORDER="0">
|
||
</a>
|
||
</h3>
|
||
</td>
|
||
<td valign="top">
|
||
<h1 align="center"><a href="index.html">boost::variant</a></h1>
|
||
<h2 align="center">Reference</h2>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<hr>
|
||
<p></p>
|
||
<ul>
|
||
<li><a href="#Synopsis">Synopsis</a></li>
|
||
<li><a href="#BoundedType">BoundedType Concept</a></li>
|
||
<li><a href="#Visitor">Visitor Concept</a></li>
|
||
<li><a HREF="#BoostVariantLimitTypes">BOOST_VARIANT_LIMIT_TYPES</a></li>
|
||
<li><a href="#variant"><code>boost::variant</code></li>
|
||
<li><a href="#Visitation"><code>boost::apply_visitor</code></a></li>
|
||
<li><a href="#StaticVisitor"><code>boost::static_visitor</code></a></li>
|
||
<li><a href="#ValueExtraction"><code>boost::get</code></a></li>
|
||
<li><a href="#incomplete"><code>boost::incomplete</code></a></li>
|
||
<li><a href="#empty"><code>boost::empty</code></a></li>
|
||
</ul>
|
||
<hr>
|
||
<h3><a name="Synopsis">Synopsis</a></h3>
|
||
<p>Dependencies and library features defined in <a href="../../../boost/variant.hpp">
|
||
"<code>boost/variant.hpp</code>"</a>:
|
||
</p>
|
||
<pre>
|
||
|
||
#define <a href="#BOOST_VARIANT_LIMIT_TYPES">BOOST_VARIANT_LIMIT_TYPES</a> <i>implementation-defined</i>
|
||
|
||
namespace boost
|
||
{
|
||
template
|
||
<
|
||
typename T1 = boost::empty,
|
||
typename T2 = <em>implementation-defined</em>,
|
||
...
|
||
typename T<i>n</i> = <em>implementation-defined</em>
|
||
>
|
||
class <a href="#variant">variant</a>;
|
||
|
||
template <typename T1, typename T2, ... , typename T<i>n</i>>
|
||
void swap(
|
||
variant<T1,T2, ... ,T<i>n</i>>&,
|
||
variant<T1,T2, ... ,T<i>n</i>>&);
|
||
|
||
|
||
template
|
||
<
|
||
BOOST_TEMPLATED_STREAM_ARGS(E,T),
|
||
BOOST_TEMPLATED_STREAM_COMMA
|
||
typename U1, typename U2, ..., typename U<i>n</i>
|
||
>
|
||
BOOST_TEMPLATED_STREAM(ostream, E, T)&
|
||
operator<<(BOOST_TEMPLATED_STREAM(ostream, E, T)& out,
|
||
const variant<U1,U2, ..., U<i>n</i>>& v);
|
||
}
|
||
</pre>
|
||
<p>Test harnesses provided in <code>"libs/variant/test"</code> directory.</p>
|
||
<p></p>
|
||
<hr>
|
||
<h2><a name="BoundedType"><i>BoundedType</i> Concept</a></h2>
|
||
<p>Given <a href="#variant"><code>variant</code></a><code><T1,T2,...,T<i>n</i>></code>,
|
||
each type <code>T<i>i</i></code> is a <b>bounded type</b> of the <code>variant</code>.</p>
|
||
<p>The requirements on bounded types are as follows:</p>
|
||
<ul>
|
||
<li><em>CopyConstructible</em> [20.1.3].</li>
|
||
<LI><em><a href="../../utility/Assignable.html">Assignable</em></a>.</LI>
|
||
<li>Destructor upholds the no-throw exception-safety guarantee.</li>
|
||
<li>Not top-level <code>const</code>-qualified.</li>
|
||
<li>Not a reference type</li>
|
||
<li>Complete at the point of variant template instantiation.
|
||
(See <code><a href="#incomplete"> incomplete<T></a></code>
|
||
wrapper for a solution to containing incomplete types.)</li>
|
||
</ul>
|
||
<p>Additional requirements on the first type T1:</p>
|
||
<ul>
|
||
<li>If <code>T1</code> is a
|
||
<a href="../../mpl/doc/ref/Sequences.html"><code>boost::mpl</code></a>
|
||
sequence,and no other types are specified (i.e: <i>n</i> = 1),
|
||
then the instantiated variant will use the types contained in
|
||
<code>T1</code> as its <em>Bounded Types</em>.
|
||
</li>
|
||
<li><code>T1</code> must be <em>DefaultConstructible</em> [20.1.4]
|
||
if the <code>variant</code> is expected to be default constructible.
|
||
</li>
|
||
</ul>
|
||
<hr>
|
||
<h2><a name="Visitor"><i>Visitor</i> Concept</a></h2>
|
||
<p>Given <a href="#variant"><code>variant</code></a><code><T1,T2,...,T<i>n</i>></code>,
|
||
a function object which unambiguously accepts any value of
|
||
each of the variant's <A HREF=#BoundedType">bounded types</A>, is a
|
||
<B>Visitor</B> of of the <CODE>variant</CODE>.
|
||
<p>Additional requirements on visitors are as follows:</p>
|
||
<ul>
|
||
<li>
|
||
Must expose inner type <code>result_type</code>,
|
||
or - alternatively - derive from
|
||
<a href="#StaticVisitor"><code>static_visitor</code></a>.
|
||
<!-- (See <code><a href="#visitor_ptr">visitor_ptr</a></code>
|
||
wrapper for a solution to functions as visitors.)--> </li>
|
||
<li>
|
||
Each overload of <code>operator()</code>must return a value
|
||
that is implicitly-convertible to <code>result_type</code>.</li>
|
||
</ul>
|
||
<h3><b>Examples:</b></h3>
|
||
<p>The following class is a visitor of a number of <code>variant</code> types
|
||
(e.g., explicitly: <code>variant<int,std::string></code>, or implicitly: <code>
|
||
variant<short,std::string></code>, etc.):</p>
|
||
<pre>class my_visitor
|
||
{
|
||
typedef int result_type;
|
||
|
||
int operator()(int i)
|
||
{
|
||
return i * 2;
|
||
}
|
||
|
||
int operator()(const std::string& s)
|
||
{
|
||
return s.length();
|
||
}
|
||
};</pre>
|
||
<p>Another example is the following class, which exposes a templated
|
||
function call operator, allowing it to operate on values of many types.
|
||
Thus, the following class is a visitor of any <code>variant</code>
|
||
whose bounded types each support streaming output:</p>
|
||
<pre>class printer
|
||
{
|
||
typedef void result_type;
|
||
|
||
template <typename T>
|
||
void operator()(const T& t)
|
||
{
|
||
std::cout << t << '\n';
|
||
}
|
||
};</pre>
|
||
<hr>
|
||
<h3><a name="BoostVariantLimitTypes">BOOST_VARIANT_LIMIT_TYPES</a></h3>
|
||
<pre>#define BOOST_VARIANT_LIMIT_TYPES <i>implementation-defined</i></pre>
|
||
<p><i>Implementation-defined</i>. Equal to the length of the template parameter
|
||
list for <code>variant</code>.</p>
|
||
<p><b>Note:</b> Conforming implementations of <code>variant</code> must allow at
|
||
least ten bounded types. That is, <code>BOOST_VARIANT_LIMIT_TYPES >= 10</code>
|
||
must evaluate true.</p>
|
||
<hr>
|
||
<h2><a name="variant"><code>boost::variant</code></a></h2>
|
||
<pre>template
|
||
<
|
||
typename T1 = boost::empty,
|
||
typename T2 = <em>implementation-defined</em>,
|
||
...
|
||
typename T<i>n</i> = <em>implementation-defined</em>
|
||
>
|
||
class variant
|
||
{
|
||
public: // <i><a href="#variant-structors">structors</a></i>
|
||
|
||
<a href="#variant-default-ctor">variant</a>();
|
||
<a href="#variant-copy-ctor">variant</a>(const variant &);
|
||
|
||
template <typename OperandType>
|
||
<a href="#variant-template-ctor">variant</a>(const OperandType &);
|
||
|
||
<a href="#dtor">~variant</a>();
|
||
|
||
public: // <i><a href="#variant-modifiers">modifiers</a></i>
|
||
|
||
variant & <a href="#variant-swap">swap</a>(variant &);
|
||
variant & <a href="#variant-copy-assign">operator=</a>(const variant &);
|
||
|
||
template <typename OperandType>
|
||
variant & <a href="#variant-template-assign">operator=</a>(const OperandType &);
|
||
|
||
public: // <i><a href="#variant-queries">queries</a></i>
|
||
|
||
int <a href="#variant-which">which</a>() const;
|
||
const std::type_info & <a href="#variant-type">type</a>() const;
|
||
|
||
bool <a href="#variant-empty">empty</a>() const;
|
||
|
||
private: // <i>representation</i>
|
||
...
|
||
};</pre>
|
||
<p>An instance of <code>variant</code> contains exactly one instance of one of its
|
||
bounded types, which are specified as arguments to <code>variant</code>'s
|
||
template parameter list. The length of <code>variant</code>'s template
|
||
parameter list is equal to the implementation defined value <a href="#BOOST_VARIANT_LIMIT_TYPES">
|
||
<code>BOOST_VARIANT_LIMIT_TYPES</code></a>.</p>
|
||
<p>Each type specified as a bounded type must satisfy the <a href="#BoundedType"><i>BoundedType</i></a>
|
||
requirements. Note that <code>variant</code> itself satisfies <a href="#BoundedType">
|
||
<i>BoundedType</i></a> requirements with default construction.
|
||
</p>
|
||
<p>All members of <code>variant</code> satisfy the strong guarantee of
|
||
exception-safety.</p>
|
||
<blockquote>
|
||
<hr>
|
||
<h3><a name="variant-structors">Structors</a></h3>
|
||
<pre><a name="variant-default-ctor">variant</a>();</pre>
|
||
<p>Default constructor. Initializes <code>*this</code> with the default value of
|
||
the first bounded type (i.e, <code>T1</code>). May fail with any exceptions
|
||
arising from the default constructor of <code>T1</code>.<br>
|
||
</p>
|
||
<pre><a name="variant-copy-ctor">variant</a>(const variant& other);
|
||
</pre>
|
||
<p>Copy constructor. Copies the content of <code>other</code> into <code>*this</code>.
|
||
May fail with any exceptions arising from the copy constructor of <code>other</code>'s
|
||
contained type.<br>
|
||
</p>
|
||
<a name="variant-template-ctor"></a>
|
||
<pre>template <typename OperandType>
|
||
variant(const OperandType & operand);
|
||
</pre>
|
||
<p>Templated constructor. Initializes <code>*this</code> according to the following
|
||
logic:</p>
|
||
<ol>
|
||
<li>
|
||
If <code>OperandType</code> is <b>not a <code>variant</code></b>:<ul>
|
||
<li>
|
||
If <code>OperandType</code> is one of the bounded types of the <code>variant</code>,
|
||
initialize <code>*this</code> with a copy of <code>operand</code>.</li>
|
||
<li>
|
||
Otherwise, use overload resolution rules to find the best conversion for <code>OperandType</code>,
|
||
and initialize <code>*this</code> with a copy of the converted <code>operand</code>.
|
||
(However, if the conversion is ambiguous, or if none exists, a compiler error
|
||
is generated.)</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
Otherwise (i.e: <code>OperandType</code> <b>is a <code>variant</code></b>):
|
||
<ul>
|
||
<li>
|
||
If <code>OperandType</code> does not appear on <code>*this</code>'s set of
|
||
types, then <code>*this</code> is initialized with <code>operand</code>'s held
|
||
value (as described in item 1, above).</li>
|
||
<li>
|
||
Otherwise, <code>operand</code> is assigned, as-is, into <code>*this</code>.
|
||
Hence, the held value of <code>*this</code> is, by itself, a variant.</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p>May fail with any exceptions arising from the copy constructor of <code>OperandType</code>.<br>
|
||
</p>
|
||
<pre><a name="variant-dtor">~variant</a>();
|
||
</pre>
|
||
<p>Non-throwing destructor that releases all resources used in management of <code>*this</code>,
|
||
including the currently contained value.
|
||
</p>
|
||
<a name="variant-modifiers"><h3>Modifiers</h3></a>
|
||
<pre>void <a name="variant-swap">swap</a>(variant& other);
|
||
</pre>
|
||
<p>Exchanges contents of <code>*this</code> and <code>other</code>. May fail with
|
||
any exceptions arising from the copy constructors of the contained types of <code>*this</code>
|
||
or <code>other</code>, or from the swap primitive of the held values, if <code>this->type()
|
||
== other.type()</code>.<br>
|
||
</p>
|
||
<pre>variant& <a name="variant-copy-assign">operator=</a>(const variant& rhs);
|
||
</pre>
|
||
<p>Copy assignment. Assigns <code>rhs</code>'s contained value into <code>*this</code>.
|
||
The old value contained by <code>*this</code> is properly destroyed.</p>
|
||
<p>Note: this operator follows the same logic as the <a href="#CopyConstructor">copy
|
||
constructor</a>.</p>
|
||
<a name="variant-template-assign"></a>
|
||
<pre>template<class OperandType>
|
||
variant& operator=(const OperandType &);
|
||
</pre>
|
||
<p>Templated assignment. Assigns <code>rhs</code> into <code>*this</code>. The old
|
||
value held by <code>*this</code> is properly destroyed.</p>
|
||
<p>Note: This operator follows the same logic as the <a href="#TemplatedConstructor">templated
|
||
constructor</a>.</p>
|
||
<a name="variant-queries"><h3>Queries</h3></a>
|
||
<a name="variant-type"></a>
|
||
<pre>const std::type_info & type() const;</pre>
|
||
<p>Non-throwing query that returns the <code>typeid()</code> of the contained value</p>
|
||
<a name="variant-empty"></a>
|
||
<pre>bool empty() const;</pre>
|
||
<p>Non-throwing query that returns <code>true</code> if, and
|
||
only if, the held value is of type
|
||
<a href="#empty"><code>boost::empty</code></a>.<br>
|
||
Consequently, if <code>boost::empty</code> is not one of the
|
||
<a href="#BoundedType">BoundedTypes</a>, then
|
||
<code>empty()</code> will always yield <code>false</code>.</p>
|
||
|
||
<a name="variant-which"></a>
|
||
<pre>int which() const;</pre>
|
||
<p>Non-throwing query that returns the zero-based index of the bounded type of the
|
||
contained value.<br>
|
||
</p>
|
||
</blockquote>
|
||
<hr>
|
||
<h2><a name="Visitation">Visitation: <code>apply_visitor</code></a></h2>
|
||
<pre>// Binary form
|
||
template<typename VisitorType, typename VariantType>
|
||
typename VisitorType::result_type apply_visitor(VisitorType& visitor,
|
||
VariantType& var_inst);
|
||
|
||
// Unary form
|
||
template<class VisitorType>
|
||
boost::apply_visitor_t<VisitorType> apply_visitor(VisitorType& visitor);
|
||
|
||
template <typename VisitorType>
|
||
class apply_visitor_t
|
||
{
|
||
public:
|
||
typedef typename VisitorType::result_type result_type;
|
||
|
||
template <typename VariantType>
|
||
result_type operator()(VariantType& var_inst);
|
||
|
||
...
|
||
};
|
||
</pre>
|
||
<p><code>boost::apply_visitor(visitor, var_inst)</code> passes the variant object, <code>
|
||
var_inst</code>, to the given visitor (<code>visitor</code>). This is
|
||
equivalent to calling <code>visitor</code>'s function-call operator, with <code>var_inst</code>'s
|
||
currently held value.<br>
|
||
<code>VisitorType</code> must be a <a href="#Visitor">visitor of</a> <code>VariantType</code>.
|
||
See <a HREF="tutorial.html#FunctorBasedVisitation">Functor-based visitation</a>
|
||
for an in-depth description of visitors.<br>
|
||
<br>
|
||
The unary form of <code>apply_visitor()</code> tranforms the given visitor into
|
||
a unary function object which accepts a variant object, thus, the following two
|
||
lines are equivalent:<br>
|
||
</p>
|
||
<pre> boost::apply_visitor(visitor, var_inst); // Binary form
|
||
boost::apply_visitor(visitor)(var_inst); // Unary form
|
||
</pre>
|
||
<p>Consequently, the unary <code>apply_visitor()</code> function, is highly useful
|
||
when <code>std::for_each</code> (or a similar STL algorithm) needs to be
|
||
applied on a sequence of <code>variant</code> objects, as illustrated in the <a href="sample.html#poly">
|
||
Polymorphism: Inheritance Vs. Variants</a> sample.<br>
|
||
</p>
|
||
<hr>
|
||
<h2><a name="StaticVisitor">Visitation: <code>static_visitor</code></a></h2>
|
||
<pre>template<typename R = void>
|
||
struct static_visitor
|
||
{
|
||
typedef R result_type;
|
||
};
|
||
|
||
</pre>
|
||
<p><code>static_visitor</code> defines the nested type <code>result_type</code>,
|
||
which is required from each <a href="#Visitor">visitor</a> class.
|
||
<br>
|
||
</p>
|
||
<hr>
|
||
<h2><a name="ValueExtraction">Value Extraction: <code>get</code></a></h2>
|
||
<pre> class bad_get : public std::exception
|
||
{
|
||
public:
|
||
virtual const char* what() const throw();
|
||
};
|
||
|
||
template<typename ToType,typename T1, typename T2, ...>
|
||
ToType& get(boost::variant<T1,T2,...>& v);
|
||
|
||
template<typename ToType,typename T1, typename T2, ...>
|
||
const ToType& get(const boost::variant<T1,T2,...>& v);
|
||
|
||
template<typename ToType,typename T1, typename T2, ...>
|
||
ToType* get(boost::variant<T1,T2,...>* v);
|
||
|
||
template<typename ToType,typename T1, typename T2, ...>
|
||
const ToType* get(const boost::variant<T1,T2,...>* v);
|
||
|
||
</pre>
|
||
Returns a reference/pointer to a held value:<ul>
|
||
<li>If a pointer is passed: Returns a pointer to the held value if its type is
|
||
<code>ToType</code>. Otherwise, returns <code>NULL</code>.</li>
|
||
<li>If a value/reference is passed: Returns a reference to the held value if its type
|
||
is <code>ToType</code>. Otherwise, throws a <code>bad_get</code> exception.<br>
|
||
</li>
|
||
</ul>
|
||
<br><hr>
|
||
<h2><a name="incomplete"><code>incomplete</code></a></h2>
|
||
<p><code>incomplete<T></code> is a class template, which allows a <code>variant</code>
|
||
type to be instantiated with incomplete types.<br>
|
||
By specifying <code>incomplete<T></code> as one of the actual template
|
||
parameters, the instantiated variant will be able to handle values of type <code>T</code>,
|
||
although <code>T</code> is incomplete at the instantiation point.<br>
|
||
<code>incomplete<></code> is typically used for solving circular
|
||
dependencies, but, more importantly, it also enables the creation of <b>recursive</b>,
|
||
variant-based, constructs.<br>
|
||
<br>
|
||
Note that using <code>incomplete<T></code> instructs the concrete
|
||
<code>variant</code> type to employ heap allocation scheme
|
||
for storing values of type <code>T</code> ("Reference Semantics"). This
|
||
is opposite to the to the standard case, where values of complete types
|
||
are stored within the <code>variant</code> object itself ("Value
|
||
Semantics").<br>
|
||
<br>
|
||
The snip below demonstrates the usage of <code>incomplete<></code>. A
|
||
complete sample program is available <a HREF="sample.html#tree">here</a>.
|
||
</p>
|
||
<pre> using boost::variant;
|
||
using boost::incomplete;
|
||
|
||
struct non_leaf_node; // Forward declaration
|
||
|
||
// Define a tree_node variant with these two types:
|
||
// (1) int, (2) non_leaf node
|
||
typedef variant
|
||
<
|
||
int,
|
||
incomplete<non_leaf_node> // non_leaf_node is incomplete at
|
||
// this point so it must be wrapperd
|
||
// by incomplete<>
|
||
> tree_node;
|
||
|
||
struct non_leaf_node
|
||
{
|
||
non_leaf_node(const non_leaf_node& other)
|
||
: left_(other.left_), right_(other.right_), num_(other.num_)
|
||
{ }
|
||
|
||
int num_;
|
||
tree_node left_;
|
||
tree_node right_;
|
||
};
|
||
|
||
</pre>
|
||
|
||
<br><hr>
|
||
<h2><a name="empty"><code>empty</code></a></h2>
|
||
<pre>
|
||
struct empty { };
|
||
|
||
bool operator==(const empty&, const empty&); // Always true
|
||
bool operator<(const empty&, const empty&); // Always false
|
||
|
||
BOOST_TEMPLATED_STREAM_TEMPLATE(E,T)
|
||
BOOST_TEMPLATED_STREAM(ostream, E,T)&
|
||
operator<<(BOOST_TEMPLATED_STREAM(ostream, E,T)&, const empty& ); // NOP
|
||
</pre>
|
||
<p>
|
||
A <code>variant</code> object holding a value of
|
||
type <code>boost::empty</code> is considered to be empty.<br>
|
||
This convention is acknowledged by <code>varinat::empty()</code>: Its
|
||
return value is <code>true</code>, if and only if the held value is of type
|
||
<code>boost::empty</code>.<br>
|
||
<br>
|
||
Public interface
|
||
<ul>
|
||
<li>Comparisons:<code>operator==(const empty&, const empty&)</code>
|
||
always returns <code>true</code>, while
|
||
<code>operator<(const empty&, const empty&)</code> always returns
|
||
<code>false</code>.</li>
|
||
<li>Output: <code>operator<<</code> has a "Do-nothing" behavior when
|
||
invoked with a <code>boost::empty</code> value.
|
||
</li>
|
||
</ul>
|
||
<br>
|
||
Comments
|
||
<ul>
|
||
<li>A <code>variant</code> object that does not have <code>boost::empty</code>
|
||
as one of its <a href="#BoundedType">BoundedTypes</a>, can never be in an empty
|
||
state. At any given point it holds a valid value.
|
||
</li>
|
||
<li>Empty variants are visitable. Consequently, every visitor of a possibly
|
||
empty variant, must accept a <code>boost::empty</code> value.
|
||
</li>
|
||
<li><code>boost::empty</code> has no state. Thus, when two instances of
|
||
<code>boost::empty</code> are tested for equality, the result is always
|
||
<code>true</code>.</li>
|
||
</ul>
|
||
<h3><b>Example:</b></h3>
|
||
<p>The following snip demonstrates an optional string value, using a variant
|
||
whose bounded types are <code>boost::empty</code> and
|
||
<code>std::string</code>:
|
||
<pre>typedef boost::variant<boost::empty,std::string> string_or_nothing;
|
||
|
||
void print(const string_or_nothing& a)
|
||
{
|
||
if(a.empty())
|
||
cout << "No string is specified" << endl;
|
||
else
|
||
cout << boost::get<const std::string>(a) << endl;
|
||
}
|
||
|
||
int main()
|
||
{
|
||
string_or_nothing s;
|
||
print(s); // Output: "No string is specified"
|
||
|
||
s = "Ford Prefect";
|
||
print(s); // Output: "Ford Prefect"
|
||
|
||
return 0;
|
||
}
|
||
</pre>
|
||
|
||
|
||
<hr>
|
||
<p>Revised 28 July 2003</p>
|
||
<p><i><EFBFBD> Copyright Eric Friedman and Itay Maman 2002-2003. All rights reserved.</i></p>
|
||
<p>Permission to use, copy, modify, distribute and sell this software and its
|
||
documentation for any purpose is hereby granted without fee, provided that the
|
||
above copyright notice appear in all copies and that both that copyright notice
|
||
and this permission notice appear in supporting documentation. Eric Friedman
|
||
and Itay Maman make no representations about the suitability of this software
|
||
for any purpose. It is provided "as is" without express or implied
|
||
warranty.</p>
|
||
</body>
|
||
</html>
|