官术网_书友最值得收藏!

Working with heterogeneous values

The need to have a value that can hold different types of data at different times during the lifetime of a program is not new. C++ supports the union construct of C, which essentially allows you to have a single type that can, at different times, assume values of different underlying POD types. POD or Plain Old Data types, roughly speaking, are types that do not require any special initialization, destruction, and copying steps and whose semantic equivalents may be created by copying their memory layouts byte for byte.

These restrictions mean that most C++ classes, including a majority of those from the Standard Library, can never be part of a union. Starting with C++11, these restrictions on a union have been relaxed somewhat, and you can now store objects of types with nontrivial construction, destruction, and copy semantics (that is, non-POD types) in a union. However, the life cycle management of such objects stored in a union is not automatic and can be a pain in the neck, hence it is best avoided.

Two libraries from Boost, Variant, and Any, provide useful variant types that provide the same functionality as unions without many of the restrictions. Using Variants and Any, storing heterogeneous data in the Standard Library containers becomes remarkably easy and error-free. These libraries represent discriminated union types. Values of a range of types can be stored in discriminated unions, and the type information is stored along with the value.

In addition to storing data of heterogeneous types, we frequently need to convert between different representations of the same data, for example, text to numeric and vice versa. Boost Conversion provides, among other things, a way to seamlessly convert between types using a uniform syntax. We look at Any, Variant, and Conversion libraries in the following sections.

Boost.Variant

Boost Variant avoids all that is wrong with C++ unions and provides a union-like construct defined over a fixed set of arbitrary types, not just POD types. We can define a variant datatype using the Boost Variant header-only library by instantiating the boost::variant template with a list of types. The list of types identifies the different types of values that the variant object can assume at different points in time. The different types in the list can be varied and unrelated, conforming to only one binding condition—that each of the types be copyable or at least movable. You may even create variants that contain other variants.

In our first example, we create a variant of an integer, a std::string, and two user-defined types Foo and Bar. With this, we illustrate the constraints on creating variant types and on operations that can be performed on such variant values:

Listing 2.4: Creating and using variants

 1 #include <boost/variant.hpp>
 2 #include <string>
 3
 4 struct Foo {
 5   Foo(int n = 0) : id_(n) {} // int convertible to Foo
 6 private:
 7   int id_;
 8 };
 9 
10 struct Bar {
11   Bar(int n = 0) : id_(n) {} // int convertible to Bar
12 private:
13   int id_;
14 };  
15 
16 int main()
17 {
18   boost::variant<Foo, int, std::string> value; // error if Foo 
19                                 // not be default constructible
20   boost::variant<std::string, Foo, Bar> value2;
21 
22 value = 1; // sets int, not Foo
23 int *pi = boost::get<int>(&value);
24 assert(pi != 0);
25 value = "foo"; // sets std::string
26 value = Foo(42); // sets Foo
27
28   // value2 = 1;             // ERROR: ambiguous - Foo or Bar?
29   // std::cout << value << ' ' << value2 << '\n'; // ERROR:
30                   // Foo, Bar cannot be streamed to ostream
31 }

We create two bare bones types: Foo (line 4) and Bar (line 10); we can initialize both implicitly from int. We define a variant called value (line 18) over three types, Foo, int, and std::string. A second variant, value2 (line 20) is defined over std::string, Foo, and Bar.

By default, each variant instance is value-initialized to an object of its first type. Thus, value is default-constructed to a Foo instance—the first type in the list of type parameters to the variant. Similarly, value2 is default-constructed to std::string—the first type in its list of type parameters. If the first type is a POD type, it is zero-initialized. Thus, the first type must be default constructible for the variant to be default constructible.

We assign an integer to value (line 22). This sets it to be an int and not Foo, which an integer is implicitly convertible to. We confirm this using the boost::get<T> function template on the address of value with T=int (line 23), and we confirm that it is not null (line 24).

We assign a const char* to value (line 25), which implicitly converts to std::string that gets stored in value, overwriting the integer value stored earlier. Next, we assign an object of Foo (line 26), which overwrites the earlier std::string value.

If we try to assign an integer to value2 (line 28, commented), it will cause a compilation error. The variable value2 being a variant defined over std::string, Foo, and Bar, an integer can implicitly be converted to either Foo or Bar and neither is a better choice—hence, it causes ambiguity and the compiler throws an error. In general, variant initialization and assignment should not result in an ambiguity over which type to instantiate within the variant.

If we try to stream the contents of value to std::cout (line 29, commented), then again, we would encounter a compilation error. This would be because one of the types (Foo) supported by the variant is not streamable, which means it cannot be written to ostreams using the insertion operator (<<).

Accessing values in a variant

We use the boost::get<T> function template to access the value of type T in a variant, where T is the concrete type of the value we want. This function, when called on a variant reference, returns a reference to the stored value or throws a boost::bad_get exception if the stored value is not of the type specified. When called on a pointer to a variant, it returns the address of the stored value or a null pointer if the stored value is not of the specified type. The latter behavior can be used to test whether a variant stores a particular type of value or not, the way it was used in listing 2.4 (line 23). This behavior of get<> closely mirrors that of dynamic_cast:

Listing 2.5: Accessing values in a variant

 1 #include <boost/variant.hpp>
 2 #include <string>
 3 #include <cassert>
 4 
 5 int main() {
 6   boost::variant<std::string, int> v1;
 7   v1 = "19937";                    // sets string
 8   int i1;
 9 
10   try {    
11     i1 = boost::get<int>(v1);      // will fail, throw
12   } catch (std::exception& e) {
13     std::cerr << e.what() << '\n';
14   }
15 
16   int *pi = boost::get<int>(&v1);  // will return null
17   assert(pi == 0);
18 
19   size_t index = v1.which();        // returns 0
20 }

In the preceding code, we create a variant v1 that can store a std::string or an int value (line 6). We set v1 to the character string "19937" (line 7). We use the boost::get<int> function to try and get an integer from v1 (line 11) but, since v1 stores a string at this point, this results in an exception being thrown. Next, we use the pointer overload of boost::get<int> that takes the address of the variant v1. This returns the pointer to the stored value if its type matches the one requested via get function's template parameter. If it does not, as in this case, a null pointer is returned (lines 16 and 17). Finally, we can get the zero-based index of the type of the value that is currently stored in the variant by calling the which member function. Since v1 contains std::string and the declared type of v1 is boost::variant<std::string, int>, therefore v1.which() should return the index of std::string in the variant's declaration—0 in this case (line 19).

Compile-time visitation

How the value stored in a variant is consumed usually depends on the type of the value. Checking a variant for each possible type using an if-else ladder can quickly aggravate the readability and maintainability of your code. Of course, we can find out the zero-based index of the type of the current value using the which member method of the variant, but it would be of little immediate use. Instead, we will look at a very elegant and versatile compile-time visitation mechanism provided by the Boost Variant library without which handling variants would be quite a drag.

The idea is to create a visitor class that contains an overloaded function call operator (operator()) to handle each type that may be stored in the variant. Using the function boost::apply_visitor, we can invoke the appropriate overload in the visitor class on a variant object, based on the type of value it contains.

The visitor class should publicly inherit from the boost::static_visitor<T> template, where T is the return type of the overloaded function call operator. By default, T is void. Let us look at an example:

Listing 2.6: Compile-time visitation of variants

 1 #include <boost/variant.hpp>
 2 
 3 struct SimpleVariantVisitor :public boost::static_visitor<void>
 4 {
 5   void operator() (const std::string& s) const
 6   { std::cout << "String: " << s << '\n'; }
 7 
 8   void operator() (long n) const
 9   { std::cout << "long: " << n << '\n'; }
10 };
11 
12 int main()
13 {
14   boost::variant<std::string, long, double> v1;
15   v1 = 993.3773;
16 
17   boost::apply_visitor(SimpleVariantVisitor(), v1);
18 }

We create a variant over the types std::string, long, and double called v1 (line 14). We set it to a value of type double (line 15). Finally, we invoke a visitor of type SimpleVariantVistor on v1 (line 17). The SimpleVariantVisitor inherits from boost::apply_visitor<void> (line 3) and contains overloads of operator() for std::string (line 5) and long (line 8) but not double. Each overload prints its argument to the standard output.

The resolution of overloads happens at compile time rather than at runtime. Thus, an overload must be available for every type of value that the variant may contain. A particular overload is invoked if its parameter type is the best match for the type of the value stored in the variant. Moreover, a single overload may handle multiple types if all the types are convertible to the type of the argument of the overload.

Interestingly, in the preceding example, there is no overload available for double. However, narrowing conversions are allowed and the overload for long is invoked with potential narrowing. In this case, the overload for long handles both long and double types. On the other hand, if we had separate overloads available for double and long but none for std::string, we would have had a compilation error. This would happen because not even a narrowing conversion would be available from std::string to either long or double, and the overload resolution would fail. Being a compile-time mechanism, this is independent of the type of the actual value stored in a variant object at any time.

Generic visitors

You may create a member function template that handles a family of types. In cases where the code for handling different types does not significantly differ, it may make sense to have such member templates. Here is an example of a visitor which prints the contents of the variant:

Listing 2.7: Generic compile-time visitation

 1 #include <boost/variant.hpp>
 2
 3 struct PrintVisitor : boost::static_visitor<>
 4 {
 5    template <typename T>
 6    void operator() (const T& t) const {
 7      std::cout << t << '\n';
 8    }
 9 };
10
11 boost::variant<std::string, double, long, Foo> v1;
12 boost::apply_visitor(PrintVisitor(), v1);

In the preceding code, we define a variant over the types std::string, double, long, and Foo. The visitor class PrintVisitor contains a generic operator(). As long as all the types in the variant are streamable, this code will compile and print the value of the variant to the standard output.

Applying visitors to variants in a container

Often, we have an STL container of variant objects, and we want to visit each object using our visitor. We can utilize the std::for_each STL algorithm and a single-argument overload of boost::apply_visitor for the purpose. The single-argument overload of boost::apply_visitor takes a visitor instance and returns a functor that applies the visitor to a passed element. The following example best illustrates the usage:

 1 #include <boost/variant.hpp>
 2
 3 std::vector<boost::variant<std::string, double, long> > vvec;
 4 …
 5 std::for_each(vvec.begin(), vvec.end(),
 6                  boost::apply_visitor(SimpleVariantVisitor()));

Defining recursive variants

The last few years have seen a phenomenal growth in the popularity of one particular data interchange format—JavaScript Object Notation or JSON. It is a simple text-based format that is often less verbose XML. Originally used as object literals in JavaScript, the format is more readable than XML. It is also a relatively simple format that is easy to understand and parse. In this section, we will represent well-formed JSON content using boost::variants and see how variants can handle recursive definitions.

The JSON format

To start with, we will look at an example of people records in the JSON notation:

    {
        "Name": "Lucas",
        "Age": 38,
        "PhoneNumbers" : ["1123654798", "3121548967"],
        "Address" : { "Street": "27 Riverdale", "City": "Newtown", 
                             "PostCode": "902739"}
    }

The preceding code is an example of a JSON object—it contains key-value pairs identifying the attributes of an unnamed object. The attribute names are quoted strings, such as "Name", "Age", "PhoneNumbers" (of which you can have more than one), and "Address". Their values could be simple strings ("Name") or numeric values ("Age"), or arrays of such values ("PhoneNumbers") or other objects ("Address"). A single colon (:) separates keys from values. The key-value pairs are separated by commas. The list of key-value pairs in an object are enclosed in curly braces. This format allows arbitrary levels of nesting as seen in the case of the "Address" attribute whose value itself is an object. You can create more nested objects that are values of attributes of other nested objects.

You may combine many such records together in an array, which are enclosed in square brackets and separated by commas:

[
    {
        "Name": "Lucas",
        "Age": 38,
        "PhoneNumbers" : ["1123654798", "3121548967"],
        "Address" : { "Street": "27 Riverdale", "City": "Newtown", 
                             "PostCode": "902739"}
    },
    {
        "Name": "Damien",
        "Age": 52,
        "PhoneNumbers" : ["6427851391", "3927151648"],
        "Address": {"Street": "11 North Ave.", "City" : "Rockport", 
                        "PostCode": "389203"}
    },
    … 
]

A well-formed JSON text contains an object or an array of zero or more objects, numeric values, strings, Booleans, or null values. An object itself contains zero or more unique attributes each represented by a unique string. The value of each attribute can be a string, numeric value, Boolean value, null value, another object, or an array of such values. Thus, the basic tokens in JSON content are numeric values, strings, Booleans, and nulls. The aggregates are objects and arrays.

Representing JSON content with recursive variants

If we were to declare a variant to represent a basic token in a JSON, it would look like this:

 1 struct JSONNullType {};
 2 boost::variant<std::string, double, bool, JSONNullType> jsonToken;

The type JSONNullType is an empty type that may be used to represent a null element in JSON.

To extend this variant to represent more complex JSON content, we will try to represent a JSON object—a key-value pair as a type. The keys are always strings, but the values can be any of the types listed above or another nested object. So, the definition of a JSON object is essentially recursive, and this is why we need a recursive variant definition to model it.

To include the definition of a JSON object in the preceding variant type, we use a metafunction called boost::make_recursive_variant. It takes a list of types and defines the resultant recursive variant type as a nested type called type. So, here is how we write a recursive definition of the variant:

 1 #define BOOST_VARIANT_NO_FULL_RECURSIVE_VARIANT_SUPPORT
 2 #include <boost/variant.hpp>
 3
 4 struct JSONNullType {};
 5
 6 typedef boost::make_recursive_variant<
 7                      std::string,
 8                      double,
 9                      bool,
10                      JSONNullType,
11                      std::map<std::string,
12                               boost::recursive_variant_>
13                     >::type JSONValue;

The #define statement on line 1 may be necessary for many compilers where the support for recursive variants, especially using make_recursive_variant, is limited.

We define the recursive variant using the boost::make_recursive_variant metafunction (line 6). In the list of types, we add a new type std::map with keys of type std::string (line 11) and values of type boost::recursive_variant_ (line 12). The special type boost::recursive_variant_ is used to indicate that the outer variant type can occur as a value in the map itself. Thus, we have captured the recursive nature of a JSON object in the variant definition.

This definition is still not complete. A well-formed JSON content may contain arrays of elements of all these different kinds. Such arrays may also be the values of an object's attributes or be nested inside other arrays. If we choose to represent an array by a vector, then an extension of the preceding definition is easy:

Listing 2.8a: Recursive variant for JSON

 1 #define BOOST_VARIANT_NO_FULL_RECURSIVE_VARIANT_SUPPORT
 2 #include <boost/variant.hpp>
 3
 4 struct JSONNullType {};
 5
 6 typedef boost::make_recursive_variant<
 7                      std::string,
 8                      double,
 9                      bool,
10                      JSONNullType,
11                      std::map<std::string,
12                               boost::recursive_variant_>,
13                      std::vector<boost::recursive_variant_>
14                     >::type JSONValue;
15
16 typedef std::vector<JSONValue> JSONArray;
17 typedef std::map<std::string, JSONValue> JSONObject;

We add one more type—std::vector<boost::recursive_variant_> (line 13)—which represents an array of JSONValue objects. By virtue of this one additional line, we now support several more possibilities:

  • A top-level array consisting of JSON objects, other JSON arrays, and the basic types of tokens
  • An array-valued attribute of an object
  • An array-valued element in another JSON array

This is the complete definition of JSONValue. In addition, we create typedefs for the recursive aggregate types—JSON arrays and JSON objects (line 16 and 17).

Visiting recursive variants

We shall now write a visitor to print JSON data stored in a variant in its standard notation. Visiting a recursive variant is not different from visiting a nonrecursive one. We still need to define overloads that can handle all types of values that the variant may store. In addition, in the overloads for the recursive aggregate types (in this case, JSONArray and JSONObject), we may need to recursively visit each of its elements:

Listing 2.8b: Visiting recursive variants

 1 void printArrElem(const JSONValue& val);
 2 void printObjAttr(const JSONObject::value_type& val); 
 3
 4 struct JSONPrintVisitor : public boost::static_visitor<void>
 5 {
 6   void operator() (const std::string& str) const
 7   {
 8     std::cout << '"' << escapeStr(str) << '"';
 9   }
10
11   void operator() (const JSONNullType&) const
12   {
13     std::cout << "null";
14   }
15
16   template <typename T>
17   void operator()(const T& value) const
18   {
19     std::cout << std::boolalpha << value;
20   }
21
22   void operator()(const JSONArray& arr) const
23   {
24     std::cout << '[';
25
26     if (!arr.empty()) {
27       boost::apply_visitor(*this, arr[0]);
28       std::for_each(arr.begin() + 1, arr.end(), printArrElem);
29     }
30 
31     std::cout << "\n";
32   }
33
34   void operator()(const JSONObject& object) const
35   {
36     std::cout << '{';
37 
38     if (!object.empty()) {
39       const auto& kv_pair = *(object.begin());
40       std::cout << '"' << escapeStr(kv_pair.first) << '"';
41       std::cout << ':';
42       boost::apply_visitor(*this, kv_pair.second);
43
44       auto it = object.begin();
45       std::for_each(++it, object.end(), printObjAttr);
46     }
47     std::cout << '}';
48   }
49
50 };
51
52 void printArrElem(const JSONValue& val) {
53   std::cout << ',';
54   boost::apply_visitor(JSONPrintVisitor(), val);
55 }
56
57 void printObjAttr(const JSONObject::value_type& val) {
58   std::cout << ',';
59   std::cout << '"' << escapeStr(val.first) << '"';
60   std::cout << ':';
61   boost::apply_visitor(JSONPrintVisitor(), val.second);
62 }

The visitor JSONPrintVisitor inherits publicly from boost::static_visitor<void> and provides overloads of operator() for the different possible types of JSON values. There is an overload for std::string (line 6), which prints strings in double quotes (line 8) after escaping any embedded quotes and other characters that need escaping. For this, we assume the availability of a function called escapeStr. We have a second overload for the JSONNullType (line 11), which just prints the string null without quotes. Other types of values, such as double or bool are handled by the member template (line 17). For bool values, it prints the unquoted strings true and false using the std::boolalpha ostream manipulator (line 19).

The main work is done by the two overloads for JSONArray (line 22) and JSONObject (line 34). The JSONArray overload prints the elements of the array enclosed in square brackets and separated by commas. It prints the first element of the vector of JSONValues (line 27) and then, applies the std::for_each generic algorithm on this vector, starting with its second element to print the subsequent elements separated by commas (line 28). For this purpose, it passes as the third argument to std::for_each, a pointer to the function printArrElem. The printArrElem (line 52) function prints each element by applying JSONPrintVisitor (line 54).

The JSONObject overload prints the elements of the map as a comma-separated list of key-value pairs. The first pair is printed as a quoted, escaped key (line 40), then a colon (line 41) followed by a call to boost::apply_visitor (line 42). Subsequent pairs are printed separated by commas from the preceding ones by iterating over the remaining elements of the map using the std::for_each and printObjAttr function pointers (line 45). The logic is analogous to that in the JSONArray overload. The printObjAttr function (line 57) prints each key-value pair passed to it, prefixing a comma (line 58), printing the escaped, quoted key (line 59), prints a colon (line 60), and invoking the visitor on the variant value (line 61).

Boost.Any

The Boost Any library takes a different route to store heterogeneous data than Boost Variant. Unlike Variant, Any allows you to store almost any type of data not limited to a fixed set and maintains the runtime type information of the stored data. Thus, it does not use templates at all and requires that Runtime Type Identification (RTTI) be enabled, while compiling the code using Boost Any (most modern compilers keep this enabled by default).

Tip

For the Boost Any library to work correctly, you must not disable the generation of RTTI for your programs.

In the following example, we create instances of boost::any to store numeric data, character arrays, and non-POD type objects:

Listing 2.9: Using Boost Any

 1 #include <boost/any.hpp>
 2 #include <vector>
 3 #include <iostream>
 4 #include <string>
 5 #include <cassert>
 6 using boost::any_cast;
 7
 8 struct MyValue {
 9   MyValue(int n) : value(n) {}
10
11   int get() const { return value; }
12
13   int value;
14 };
15
16 int main() {
17 boost::any v1, v2, v3, v4;
18
19 assert(v1.empty());
20   const char *hello = "Hello";
21 v1 = hello;
22 v2 = 42;
23 v3 = std::string("Hola");
24   MyValue m1(10);
25 v4 = m1;
26
27   try {
28 std::cout << any_cast<const char*>(v1) << '\n';
29 std::cout << any_cast<int>(v2) << '\n';
30 std::cout << any_cast<std::string>(v3) << '\n';
31 auto x = any_cast<MyValue>(v4);
32 std::cout << x.get() << '\n';
33   } catch (std::exception& e) {
34     std::cout << e.what() << '\n';
35   }
36 }

You can also use a nonthrowing version of any_cast by passing the address of an any object instead of a reference. This returns a null pointer, instead of throwing an exception if the stored type does not match the type it is cast to. The following snippet illustrates this:

 1 boost::any v1 = 42;2 boost::any v2 = std::string("Hello");
 3 std::string *str = boost::any_cast<std::string>(&v1);
 4 assert(str == nullptr);
 5 int *num = boost::any_cast<int>(&v2);
 6 assert(num == nullptr);
 7
 8 num = boost::any_cast<int>(&v1);
 9 str = boost::any_cast<std::string>(&v2);
10 assert(num != nullptr);
11 assert(str != nullptr);

We pass the address of any objects to any_cast (lines 3, 5, 8, and 9), and it returns null unless the type parameter to any_cast matches the type of the value stored in the any object. Using the pointer overload of any_cast, we can write a generic predicate to check whether an any variable stores a value of a given type:

template <typename T>
bool is_type(boost::any& any) {
  return ( !any.empty() && boost::any_cast<T>(&any) );
}

This is how you would use it:

boost::any v1 = std::string("Hello");
assert( is_type<std::string>(v1) );

This behavior of boost::any_cast emulates how dynamic_cast works.

In listing 2.9, we used different instances of the type boost::any to store different types of values. But the same instance of boost::any can store different types of values at different times. The following snippet illustrates this using the swap member function of any:

 1 boost::any v1 = 19937;
 2 boost::any v2 = std::string("Hello");
 3
 4 assert(boost::any_cast<int>(&v1) != nullptr);
 5 assert(boost::any_cast<std::string>(&v2) != nullptr);
 6
 7 v1 = 22.36;
 8 v1.swap(v2);
 9 assert(boost::any_cast<std::string>(&v1) != nullptr);
10 assert(boost::any_cast<double>(&v2) != nullptr);

We first assign a value of type double to v1 (line 7), which was carrying a value of type int (line 1). Next, we swap the contents of v1 with v2 (line 8), which was carrying a value of type std::string (line 2). We can now expect v1 to contain a std::string value (line 9) and v2 to contain a double value (line 10).

Besides using the pointer overload of any_cast, we can also use the type member function of any to access the type of the stored value:

Listing 2.10: Accessing type information in Any

boost::any value;
value = 20;
if (value.type().hash_code() == typeid(int).hash_code()) {
  std::cout << boost::any_cast<int>(value) << '\n';
}

The type member function of any returns an object of std::type_info (defined in the Standard Library header <typeinfo>). To check whether this type is the same as a given type, we compare it with the type_info object obtained by applying the typeid operator on the given type (in this case, it is int). Instead of directly comparing the two type_info objects, we compare their hash codes obtained using the hash_code member function of type_info.

Boost.Conversion

If you have ever tried parsing a text input (from a file, standard input, network, and so on) and tried a semantic translation of the data in it, you would have possibly felt the need for an easy way to convert text to numeric values. The opposite problem is to write text output based on values of numeric and textual program variables. The basic_istream and basic_ostream classes provide facilities for reading and writing specific types of values. However, the programming model for such uses is not very intuitive or robust. The C++ Standard Library and its extensions offer various conversion functions with various degrees of control, flexibility, and a general lack of usability. For example, there exists a whole slew of functions that convert between numeric and character formats or the other way round (for example, atoi, strtol, strtod, itoa, ecvt, fcvt, and so on). If we were trying to write generic code for converting between types, we would not even have the option of using any of these functions, which only work for conversions between specific types. How can we define a generic conversion syntax that can be extended to arbitrary types?

The Boost Conversion library introduces a couple of function templates that provide a very intuitive and uniform conversion syntax, which can also be extended through user-defined specializations. We will look at the conversion templates one by one.

lexical_cast

The lexical_cast function template can be used to convert a source type to a target type. Its syntax resembles the syntax of various C++ casts:

#include <boost/lexical_cast.hpp>
namespace boost {
template <typename T, typename S>
T lexical_cast (const S& source);
}

The following example shows how we can use lexical_cast to convert a string to an integer:

Listing 2.11: Using lexical_cast

 1 std::string str = "1234";
 2
 3 try {
 4   int n = boost::lexical_cast<int>(str);
 5   assert(n == 1234);
 6 } catch (std::exception& e) {
 7   std::cout << e.what() << '\n';
 8 }

We apply lexical_cast (line 4) to convert a value of type std::string to a value of int. The beauty of this approach is that it can provide a uniform syntax to all conversions and can be extended to new types. If the string does not contain a valid numeric string, then the lexical_cast invocation will throw an exception of type bad_lexical_cast.

Overloads of the lexical_cast function template are provided to allow the conversion of a part of a character array:

#include <boost/lexical_cast.hpp>
namespace boost {
template <typename T >
T lexical_cast (const char* str, size_t size);
}

We can use the preceding function in the following way:

 1 std::string str = "abc1234";
 2
 3 try {
 4   int n = boost::lexical_cast<int>(str.c_str() + 3, 4);
 5   assert(n == 1234);
 6 } catch (std::exception& e) {
 7   std::cout << e.what() << '\n';
 8 }

When converting objects of types that are streamable, lexical_cast streams the objects to an ostream object, such as an instance of stringstream, and reads it back as the target type.

Tip

A streamable object can be converted to a stream of characters and inserted into an ostream object, such as an instance of stringstream. In other words, a type T, such that ostream& operator<<(ostream&, const T&), is defined is said to be streamable.

Setting up and tearing down stream objects for each such operation incurs some overhead. As a result, in some cases, the default version of lexical_cast may not give you the best possible performance. In such cases, you may specialize the lexical_cast template for the set of types involved, and use a fast library function or provide your own fast implementation. The Conversion library already takes care of optimizing lexical_cast for all common type pairs.

Besides the lexical_cast template, there are other templates available for conversion between different numeric types (boost::numeric_cast), downcasts and cross-casts in class hierarchies (polymorphic_downcast, polymorphic_cast). You can refer to the online documentation for more information on these features.

主站蜘蛛池模板: 三台县| 新疆| 嘉黎县| 收藏| 如东县| 龙南县| 蒲城县| 法库县| 织金县| 华蓥市| 明星| 望奎县| 天门市| 仁寿县| 曲水县| 东兴市| 资阳市| 连平县| 荆州市| 沛县| 吉木乃县| 庆城县| 五华县| 盐山县| 开原市| 甘德县| 潢川县| 天气| 祁东县| 云霄县| 香港| 敦煌市| 长武县| 弋阳县| 嘉峪关市| 和田县| 九江县| 葵青区| 石楼县| 甘孜| 大田县|