Specialized Library Facilities
C++11 added several useful library facilities for specialized tasks.
This chapter focuses on:
tuple
bitset
regular expressions
random numbers
advanced IO
17.1 The tuple Type
tuple: A fixed-size collection of unnamed members whose types may differ.
A tuple is similar to pair, but it may contain any number of elements.
Required header:
#include <tuple>
A tuple is useful when several values should be grouped together without defining a separate class or struct.
17.1.1 Defining and Initializing tuples
Define a tuple by listing its member types.
std::tuple<
std::string,
std::size_t,
double
> item;
The tuple has three members.
Direct Initialization
std::tuple<
std::size_t,
std::size_t,
std::size_t
> point{
10,
20,
30
};
Each initializer initializes the corresponding tuple member.
make_tuple()
make_tuple() creates a tuple while deducing the member types.
auto item =
std::make_tuple(
"0-999-78345-X",
3,
20.0
);
The resulting type is inferred from the arguments.
Accessing Members with get
Tuple members do not have names such as first and second.
Use get<I>().
auto item =
std::make_tuple(
std::string("book"),
3,
20.0
);
std::cout
<< std::get<0>(item)
<< '\n';
std::cout
<< std::get<1>(item)
<< '\n';
get<0>() accesses the first member.
Modifying a Tuple Member
For a nonconst lvalue tuple, get<I>() returns a reference.
std::get<1>(item) = 10;
The second member is modified.
tuple_size
tuple_size reports the number of members in a tuple type.
using ItemType =
decltype(item);
constexpr std::size_t count =
std::tuple_size<
ItemType
>::value;
tuple_element
tuple_element<I, T>::type gives the type of a tuple member.
using MemberType =
std::tuple_element<
1,
ItemType
>::type;
For the example above, MemberType is the type of the second member.
Comparing Tuples
Tuples support equality and relational comparison when their corresponding members support the required operators.
std::tuple<int, int> a{
1,
2
};
std::tuple<int, int> b{
1,
3
};
bool result =
a < b;
Tuple ordering is lexicographical.
Conceptually:
compare first members
↓
if equal, compare second
↓
continue until different
17.1.2 Using a tuple to Return Multiple Values
A tuple can return several related values from one function.
Suppose a search function needs to return:
bookstore index
first matching iterator
one-past-last matching iterator
A tuple can represent the result.
using Matches =
std::tuple<
std::size_t,
std::vector<
Sales_data
>::const_iterator,
std::vector<
Sales_data
>::const_iterator
>;
Return a value:
return std::make_tuple(
store_index,
begin,
end
);
Read the result:
auto result =
find_book(...);
auto store =
std::get<0>(result);
auto first =
std::get<1>(result);
auto last =
std::get<2>(result);
A tuple is convenient when the grouped values are temporary implementation data rather than a concept that deserves its own named class.
17.2 The bitset Type
bitset: A fixed-size sequence of bits.
Required header:
#include <bitset>
Unlike integral bit operations, bitset can represent more bits than the largest built-in integer type.
Its size is part of the type and must be a constant expression.
17.2.1 Defining and Initializing bitsets
Default construction creates all-zero bits.
std::bitset<8> bits;
Result:
00000000
Initialize from an Unsigned Value
std::bitset<8> bits(
0x0F
);
Result:
00001111
The low-order bits of the unsigned value are copied into the bitset.
If the bitset is larger, additional high-order bits are zero.
If it is smaller, excess high-order bits from the initializer are discarded.
Bit Positions
Bit positions begin at zero.
bit 0
lowest-order bit
bit N - 1
highest-order bit
Initialize from a String
std::bitset<8> bits(
std::string("1100")
);
The rightmost character initializes bit zero.
The string and bitset index directions are therefore opposite.
string: 1 1 0 0
high low
bitset:
bit 3 = 1
bit 2 = 1
bit 1 = 0
bit 0 = 0
17.2.2 Operations on bitsets
Important inspection operations include:
| Operation | Meaning |
|---|---|
any() | Whether at least one bit is 1 |
none() | Whether all bits are 0 |
all() | Whether all bits are 1 |
count() | Number of bits equal to 1 |
size() | Total number of bits |
test(pos) | Value of a selected bit |
Example:
std::bitset<8> bits(
1U
);
std::cout
<< bits.any()
<< '\n';
std::cout
<< bits.count()
<< '\n';
Changing Bits
Important modification operations are:
set()
reset()
flip()
Set every bit:
bits.set();
Clear every bit:
bits.reset();
Reverse every bit:
bits.flip();
Changing One Bit
bits.set(3);
bits.reset(1);
bits.flip(0);
A position argument affects only that bit.
Subscript Access
bits[0] = true;
bool value =
bits[0];
The nonconst subscript returns a proxy object that allows modification.
Converting a bitset
Convert to an unsigned integer:
unsigned long value =
bits.to_ulong();
or:
unsigned long long value =
bits.to_ullong();
If the bit pattern cannot fit in the destination type, the conversion throws std::overflow_error.
Converting to a String
std::string text =
bits.to_string();
This produces a textual sequence of 0 and 1.
Bitset IO
A bitset can be written directly to a stream.
std::bitset<8> bits(
0x0F
);
std::cout << bits;
Output:
00001111
Input reads a sequence of valid bit characters.
17.3 Regular Expressions
Regular Expression: A pattern that describes a sequence of characters.
Required header:
#include <regex>
Important components include:
| Facility | Meaning |
|---|---|
regex | Stores a regular-expression pattern |
regex_search() | Finds a matching subsequence |
regex_match() | Tests whether the whole sequence matches |
regex_replace() | Replaces matching text |
smatch | Holds match results for string input |
sregex_iterator | Iterates through all matches |
ssub_match | Represents a submatch |
17.3.1 Using the Regular Expression Library
Construct a regex:
std::regex pattern(
"[[:alpha:]]+"
);
Search a string:
std::string text =
"123 hello 456";
std::smatch result;
if (std::regex_search(
text,
result,
pattern))
{
std::cout
<< result.str()
<< '\n';
}
Output:
hello
regex_search() vs. regex_match()
regex_search() succeeds when any substring matches.
std::regex_search(
text,
result,
pattern
);
regex_match() succeeds only when the entire input matches.
std::regex_match(
text,
pattern
);
Conceptually:
regex_search
find matching part
regex_match
entire input must match
Match Results
smatch stores information from a successful match.
std::smatch result;
std::regex_search(
text,
result,
pattern
);
Useful operations include:
result.str();
result.position();
result.length();
regex_error
Constructing or using an invalid regular expression may throw:
std::regex_error
Example:
try
{
std::regex pattern(
"["
);
}
catch (
const std::regex_error& error)
{
std::cerr
<< error.what()
<< '\n';
}
Regular-expression errors are generally detected at run time.
17.3.2 The Match and Regex Iterator Types
Use sregex_iterator to process every match in a string.
std::string text =
"cat dog cat";
std::regex pattern(
"cat"
);
for (
std::sregex_iterator iter(
text.begin(),
text.end(),
pattern
),
end;
iter != end;
++iter)
{
std::cout
<< iter->str()
<< '\n';
}
The iterator internally performs repeated regex_search() operations.
Prefix and Suffix
A match result can provide text before and after a match.
iter->prefix().str();
iter->suffix().str();
These return ssub_match objects representing the surrounding input.
This is useful when match context is needed.
ssub_match
An ssub_match represents either:
- the complete match, or
- one subexpression
Useful operations include:
match.str();
match.length();
match.matched;
17.3.3 Using Subexpressions
Parentheses define subexpressions inside a regular expression.
Example:
std::regex phone(
"(\\d{3})-(\\d{3})-(\\d{4})"
);
The complete match is:
group 0
```
Parenthesized groups are:
~~~text
group 1
group 2
group 3
Search:
std::string text =
"555-123-4567";
std::smatch result;
if (std::regex_search(
text,
result,
phone))
{
std::cout
<< result[1].str()
<< '\n';
std::cout
<< result[2].str()
<< '\n';
std::cout
<< result[3].str()
<< '\n';
}
Why Subexpressions Matter
Subexpressions let the program identify meaningful parts of a larger match.
Conceptually:
complete phone number
|
+-- area code
+-- exchange
+-- number
17.3.4 Using regex_replace
regex_replace() creates output in which matching text is replaced according to a format string.
std::string text =
"555-123-4567";
std::regex phone(
"(\\d{3})-(\\d{3})-(\\d{4})"
);
std::string result =
std::regex_replace(
text,
phone,
"($1) $2-$3"
);
Result:
(555) 123-4567
The format string can refer to captured subexpressions.
$1
$2
$3
```
---
## 17.4 Random Numbers
The C++ random-number library separates random generation into two parts:
1. an engine
2. a distribution
Required header:
~~~cpp
#include <random>
Random-Number Engine
Random-Number Engine: Generates a sequence of unsigned integer values.
Example:
std::default_random_engine engine;
Calling the engine:
auto value =
engine();
returns the next number in the engine's sequence.
Random-Number Distribution
Distribution: Transforms engine output into values with a specified statistical distribution.
Example:
std::uniform_int_distribution<int>
distribution(
1,
6
);
Generate a die roll:
int roll =
distribution(engine);
The engine provides raw pseudorandom values.
The distribution converts them into the desired range and distribution.
17.4.1 Random-Number Engines and Distribution
A common pattern is:
std::default_random_engine engine;
std::uniform_int_distribution<int>
distribution(
1,
6
);
for (int i = 0;
i != 10;
++i)
{
std::cout
<< distribution(engine)
<< ' ';
}
Engines Produce Repeatable Sequences
A default-constructed engine begins from a defined initial state.
Therefore two engines initialized the same way generate the same sequence.
This is useful for reproducible tests.
Engine State Must Persist
Do not recreate the engine for every generated value.
Bad pattern:
for (int i = 0;
i != 10;
++i)
{
std::default_random_engine
engine;
std::cout
<< engine()
<< '\n';
}
Each iteration recreates the same initial state and can repeat the same result.
Keep the engine outside the loop.
Seeding an Engine
A seed changes the starting point of the generated sequence.
std::default_random_engine
engine(
seed
);
or:
engine.seed(seed);
The same seed reproduces the same sequence.
Different seeds generally produce different sequences.
min() and max()
An engine reports its output range.
auto low =
engine.min();
auto high =
engine.max();
Applications normally use a distribution rather than manually scaling raw engine output.
Uniform Integer Distribution
std::uniform_int_distribution<int>
dice(
1,
6
);
int value =
dice(engine);
Each integer in the specified range is generated according to the uniform distribution.
17.4.2 Other Kinds of Distributions
The library provides distributions for different types and probability models.
Uniform Real Distribution
std::uniform_real_distribution<double>
distribution(
0.0,
1.0
);
double value =
distribution(engine);
This generates floating-point values over the requested range.
Normal Distribution
std::normal_distribution<double>
distribution(
0.0,
1.0
);
Arguments represent:
mean
standard deviation
```
Use:
~~~cpp
double value =
distribution(engine);
to generate normally distributed values.
Bernoulli Distribution
bernoulli_distribution produces bool results.
std::bernoulli_distribution
choose(
0.5
);
bool result =
choose(engine);
With probability 0.5, the result is true.
A different probability can be supplied:
std::bernoulli_distribution
choose(
0.55
);
Distribution State
Some distribution objects may retain state.
Therefore, like engines, distributions should usually remain alive across repeated generation rather than being recreated unnecessarily inside a loop.
17.5 The IO Library Revisited
Chapter 8 introduced the basic stream library.
This section covers three more specialized capabilities:
formatted IO
unformatted IO
random access
17.5.1 Formatted Input and Output
Every stream maintains formatting state.
That state controls details such as:
- integer base
- floating-point precision
- notation
- field width
- alignment
- fill character
Manipulators modify this state.
Boolean Formatting
By default:
std::cout
<< true
<< ' '
<< false;
prints:
1 0
Use:
std::cout
<< std::boolalpha
<< true
<< ' '
<< false;
Output:
true false
Restore numeric form:
std::cout
<< std::noboolalpha;
Integer Bases
Manipulators:
dec
oct
hex
```
Example:
~~~cpp
int value = 20;
std::cout
<< std::dec
<< value
<< '\n';
std::cout
<< std::oct
<< value
<< '\n';
std::cout
<< std::hex
<< value
<< '\n';
These control how integral values are formatted.
Showing the Base
Use:
std::showbase
to display prefixes associated with nondecimal bases.
std::cout
<< std::showbase
<< std::hex
<< 20;
Disable with:
std::noshowbase
Uppercase Formatting
std::uppercase
uses uppercase letters where the output format has alphabetic components.
Disable with:
std::nouppercase
Floating-Point Precision
Required header for parameterized manipulators:
#include <iomanip>
Set precision:
std::cout
<< std::setprecision(4)
<< 3.1415926;
The meaning of precision depends on the selected floating-point notation.
Floating-Point Notation
Important manipulators include:
fixed
scientific
hexfloat
defaultfloat
Example:
std::cout
<< std::fixed
<< std::setprecision(2)
<< 3.14159;
Output:
3.14
Showing the Decimal Point
Use:
std::showpoint
to force display of a decimal point and trailing zeros according to the format state.
Disable with:
std::noshowpoint
Width
setw() controls the minimum width of the next formatted value.
std::cout
<< std::setw(10)
<< 42;
Unlike many other formatting settings, width applies only to the next output item.
Alignment
Use:
left
right
internal
```
Example:
~~~cpp
std::cout
<< std::left
<< std::setw(10)
<< "value";
Fill Character
std::cout
<< std::setfill('*')
<< std::setw(8)
<< 42;
The fill character is used for unused field positions.
Formatting State Usually Persists
Many manipulators remain active until changed again.
For example:
std::cout
<< std::hex
<< 20
<< ' '
<< 30;
both integers are printed in hexadecimal.
Restore decimal explicitly when needed.
std::cout
<< std::dec;
Input Whitespace
Formatted input normally skips whitespace.
Use:
std::noskipws
to prevent automatic skipping.
Restore normal behavior with:
std::skipws
17.5.2 Unformatted Input/Output Operations
Unformatted IO: Reads or writes characters or raw byte sequences without formatted type conversion.
These operations are lower level and more error-prone than ordinary formatted IO.
Prefer higher-level operations when they are sufficient.
Single-Character Input
Read one character:
int ch =
std::cin.get();
The return type is int so the result can represent both:
- every possible character value
- end-of-file
Do not store the result immediately in char when testing against EOF.
put()
Write one character:
std::cout.put('A');
peek()
Inspect the next input character without removing it.
int ch =
std::cin.peek();
The character remains available for the next read.
unget()
unget() moves the stream back so the most recently read character can be read again.
std::cin.unget();
putback()
putback(ch) places a character back into the input stream when allowed by the stream.
std::cin.putback('A');
Multi-Byte get()
char buffer[100];
input.get(
buffer,
100,
'\n'
);
The delimiter is not stored and remains in the input stream.
Unformatted getline()
input.getline(
buffer,
100,
'\n'
);
Unlike get(), this version reads and discards the delimiter.
read()
char buffer[100];
input.read(
buffer,
100
);
read() reads a specified number of bytes into a character array.
This operation is useful for low-level or binary-style IO.
write()
output.write(
buffer,
count
);
This writes the requested number of bytes.
gcount()
gcount() reports how many characters were read by the most recent unformatted input operation.
input.read(
buffer,
100
);
auto count =
input.gcount();
Call gcount() before another unformatted input operation changes that information.
ignore()
Discard characters from an input stream.
input.ignore(
100,
'\n'
);
This is useful for removing unwanted delimiters or leftover input.
17.5.3 Random Access to a Stream
Some streams support repositioning the location of the next read or write.
This is most useful with:
fstream
stringstream
```
Ordinary terminal streams such as `cin` and `cout` generally do not support useful random access.
---
### Stream Position Marker
Seekable streams maintain a current position marker.
The next read or write occurs at that position.
---
### `tellg()` and `tellp()`
For input:
~~~cpp
auto position =
input.tellg();
For output:
auto position =
output.tellp();
The returned type is a stream position type.
seekg() and seekp()
Move to an absolute position:
input.seekg(position);
or:
output.seekp(position);
Relative Seeking
A stream can move relative to:
beg
cur
end
```
Example:
~~~cpp
file.seekg(
0,
std::ios::end
);
Move relative to the current position:
file.seekg(
-10,
std::ios::cur
);
Input and Output Versions
The suffixes mean:
g
get / input
p
put / output
```
Input-oriented streams use:
~~~text
tellg
seekg
```
Output-oriented streams use:
~~~text
tellp
seekp
```
Read/write streams such as `fstream` can use both interfaces.
---
### One Logical Position
For read/write streams, the library conceptually maintains one current position in the shared stream buffer.
Therefore when switching between reading and writing, reposition the stream appropriately.
---
### Random Access Is System Dependent
Random access behavior can depend on:
- stream type
- file mode
- operating system
- text vs. binary representation
Use seek/tell operations only when the underlying stream supports them.
---
## Essential Study Checklist
1. `tuple` stores a fixed number of unnamed values whose types may differ.
2. `make_tuple()` deduces tuple member types, and `get<I>()` accesses a member by position.
3. `tuple_size` gives the number of tuple members, while `tuple_element` gives a selected member type.
4. `tuple` is useful for returning several temporary related values without defining a separate class.
5. `bitset<N>` stores a fixed compile-time number of bits.
6. `bitset` can be initialized from unsigned values or strings, and string indexing runs opposite to bit numbering.
7. `any()`, `none()`, `all()`, `count()`, and `test()` inspect bit state.
8. `set()`, `reset()`, and `flip()` modify all bits or selected bits.
9. `regex_search()` finds a matching substring, whereas `regex_match()` requires the whole input to match.
10. `smatch` stores match information, and `sregex_iterator` iterates through repeated matches.
11. Parenthesized regular-expression subexpressions can be accessed through match results.
12. `regex_replace()` rewrites matching text using a replacement format.
13. C++ random generation separates a random-number engine from a distribution.
14. Engines retain state and should not normally be recreated inside a generation loop.
15. A seed determines the starting state of an engine and makes generated sequences reproducible.
16. `uniform_int_distribution`, `uniform_real_distribution`, `normal_distribution`, and `bernoulli_distribution` produce different statistical forms.
17. IO manipulators control persistent formatting state such as base, precision, notation, and alignment; `setw()` affects only the next formatted value.
18. Unformatted IO provides low-level operations such as `get`, `put`, `read`, `write`, `peek`, `ignore`, and `gcount`.
19. Seekable streams use `tellg`/`tellp` to inspect position and `seekg`/`seekp` to reposition it.
20. Random-access IO is mainly useful for file and string streams and is partly system dependent.