- Learning Boost C++ Libraries
- Arindam Mukherjee
- 3946字
- 2021-07-16 20:49:02
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 (<<
).
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).
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.
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.
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()));
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.
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.
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).
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).
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.
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.
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.
- Learning Single:page Web Application Development
- The Supervised Learning Workshop
- Web全棧工程師的自我修養(yǎng)
- JS全書:JavaScript Web前端開發(fā)指南
- Selenium Testing Tools Cookbook(Second Edition)
- Creating Data Stories with Tableau Public
- 石墨烯改性塑料
- Ext JS 4 Plugin and Extension Development
- Oracle Database XE 11gR2 Jump Start Guide
- Java并發(fā)實現(xiàn)原理:JDK源碼剖析
- JBoss AS 7 Development
- 微信公眾平臺開發(fā)最佳實踐
- Mastering Kali Linux for Advanced Penetration Testing(Second Edition)
- Learning Puppet for Windows Server
- Python數(shù)據(jù)分析實戰(zhàn)