본문으로 건너뛰기

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:

OperationMeaning
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:

FacilityMeaning
regexStores a regular-expression pattern
regex_search()Finds a matching subsequence
regex_match()Tests whether the whole sequence matches
regex_replace()Replaces matching text
smatchHolds match results for string input
sregex_iteratorIterates through all matches
ssub_matchRepresents 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.