본문으로 건너뛰기

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.