# 第六章 函式 練習
## 練習 6.1
參數和引數之間的差異為何?
解:
引數是函式呼叫的實際值,是參數的初始值
## 練習 6.2
指出下列函式何者是錯的,並描述原因,以及你會如何進行更正
```cpp
(a)int f() {
string s;
// ...
return s;
}
(b)f2(int i) { /* ... */
}
(c)int calc(int v1, int v1) { /* ... */
}
(d)double square(double x) return x *
```
解:
- (a)回傳型別應改為 string
- (b)應加上回傳型別 void
- (c)參數不可為同名
- (d)函式主體一定要加上大括號(curly braces)
## 練習 6.3
撰寫並測試你自己版本的 fact
解:
```cpp
#include <iostream>
using std::cerr;
using std::cout;
using std::endl;
int fact(int i) {
if (i < 0)
cerr << "Input cannot be a negative number.";
return i > 1 ? i * fact(i - 1) : 1;
}
int main() {
cout << fact(5) << endl;
return 0;
}
```
## 練習 6.4
編寫一個與使用者互動的函式,請求輸入一個數字並產生該數字的階乘。從 main 呼叫此函式
```cpp
#include <iostream>
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
int fact(int i) { return i > 1 ? i * fact(i - 1) : 1; }
void userInput() {
int n;
cout << "Enter a number within [1, 11):" << endl;
while (cin >> n) {
if (n < 1 || n > 10) {
cout << "Out of range, please try again." << endl;
continue;
}
cout << fact(n) << endl;
cout << "Enter a number within [1, 11):" << endl;
}
}
int main() {
userInput();
return 0;
}
```
## 練習 6.5
傳寫一個函式回傳其引數的絕對值(absolute value)
```cpp
#include <iostream>
using std::cout;
using std::endl;
int abs(int i) { return i > 0 ? i : -i; }
int main() {
cout << abs(-5) << endl;
return 0;
}
```
## 練習 6.6
解釋參數、區域變數,以及區域 static 變數之間的差異。各自給出一個函式為例
解:
參數定義在函式的參數列表裡面,而區域變數定義在程式主體裡面,區域靜態變數在程式第一次經過它的定義述句時被初始化,並且直到程式終止時才被摧毀
```cpp
#include <iostream>
using std::cout;
using std::endl;
int count_add(int n) {
static int ctr = 0;
ctr += n;
return ctr;
}
int main() {
for (int i = 0; i != 10; ++i)
cout << count_add(i) << endl;
return 0;
}
```
## 練習 6.7
撰寫一個會在它第一次被呼叫時回傳 0,然後之後每次被呼叫就依序產生對應數字的函式
解:
```cpp
#include <iostream>
using std::cout;
using std::endl;
int generate() {
static int ctr = 0;
return ctr++;
}
int main() {
for (int i = 0; i != 10; ++i)
cout << generate() << endl;
return 0;
}
```
## 練習 6.8
撰寫一個名為 Chapter6.h 的標頭檔,其中含有你為 6.1 中的習題所寫的函式的宣告
解:
```cpp
// 使用練習 6.4 的程式
int fact(int i);
void userInput();
```
## 練習 6.9
寫出你自己版本的 fact.cc 和 factMain.cc 檔案。這些檔案應該引入你來自前一節習題的 Chapter6.h。使用這些檔案來這些檔案來了解你的編譯器如何支援個別編譯
解:
```cpp
// fact.cpp
#include "Chapter6.h"
#include <iostream>
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
int fact(int i) { return i > 1 ? i * fact(i - 1) : 1; }
void userInput() {
int n;
cout << "Enter a number within [1, 11) :" << endl;
while (cin >> n) {
if (n < 1 || n > 10) {
cout << "Out of range, please try again." << endl;
continue;
}
cout << fact(n) << endl;
cout << "Enter a number within [1, 11) :" << endl;
}
}
```
```cpp
// factMain.cpp
#include "Chapter6.h"
#include <iostream>
int main() {
cout << "5! is:" << fact(5) << endl;
userInput();
}
```
編譯指令:g++ factMain.cpp fact.cpp -o main
## 練習 6.10
使用指標撰寫一個函式來將兩個 int 的值對調(swap)。呼叫此函式並印出對調後的值來測試之
解:
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
void swap(int *n, int *m) {
int tmp = *n;
*n = *m;
*m = tmp;
}
int main() {
for (int n, m; cout << "Please Enter:\n", cin >> n >> m;) {
swap(&n, &m);
cout << n << " " << m << endl;
}
return 0;
}
```
## 練習 6.11
撰寫並測試接受一個參考的你自己版本的 reset
解:
```cpp
#include <iostream>
using std::cout;
using std::endl;
void reset(int &i) { i = 0; }
int main() {
int i = 42;
reset(i);
cout << i << endl;
return 0;
}
```
## 練習 6.12
改寫 6.2.1 中練習 6.10 的程式,使用參考而非指標來對調兩個 int 的值。你認為哪個版本比較容易使用,原因何在?
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
void swap(int &n, int &m) {
int temp = n;
n = m;
m = temp;
}
int main() {
for (int n, m; cout << "Please Enter:\n", cin >> n >> m;) {
swap(n, m);
cout << n << " " << m << endl;
}
return 0;
}
```
參考更好用,呼叫的介面也更為簡潔
## 練習 6.13
假設 T 是一個型別的名稱,請解釋宣告 void f(T) 和宣告 void f(T&) 的函式之間的差異
解:
void f(T) 的參數是透過數值複製傳遞,在函式中的 T 參數是引數的拷貝,改變 T 不會影響到原來的引數
void f(&T) 的參數是透過參考傳遞,在函式中的 T 是引數的參考,改變 T 會影響到原來的引數
## 練習 6.14
給出例子說明何時一個參數應該是參考型別。也給出參數不應該是參考的一個例子
解:
例如交換兩個整數的函式,參數應該使用參考
```cpp
void swap(int &n, int &m) {
int temp = n;
n = m;
m = temp;
}
```
當引數的值是右值時,參數不能為參考型別,否則導致編譯錯誤
```cpp
int add(int a, int b) { return a + b; }
int main() {
int i = add(1, 2);
return 0;
}
```
## 練習 6.15
解釋 find_char 的每個參數之型別背後的原因。特別是,為什麼 s 是對 const 的一個參考,但 occurs 卻是一個普通的參考?為甚麼這些參數是參考,但 char 參數 c 卻不是?如果我們讓 s 是一個普通的參考,可能會發生什麼事?如果讓 occurs 是對 const 的一個參考會怎麼樣呢?
解:
1. 因為 string 很長,使用引數可以避免拷貝
2. 而在函式中我們不希望改變 s 的內容,所以令 s 為常數
3. occurs 是要傳遞到函式外部的變數,所以使用參考,occurs 的值會改變,所以是普通參考
4. 我們只需要 c 的值,這個引數可能是右值(右值引數無法用於左值參考參數),因此 c 不使用參考型別
5. 如果 s 是普通參考,也可能會意外改變原來字串的內容
6. occurs 如果是常數參考,那麼就代表不能改變它原來的值,那也就失去意義了
## 練習 6.16
下列函式,雖然是合法的,但其用處比預期低。請找出並解除這個函式所受的限制
```cpp
bool is_empty(string& s) { return s.empty(); }
```
解:
侷限性在於常數字串與字串字面值無法作為該函式的引數,如果下面這要使用會是不合法的:
```cpp
const string str;
bool flag = is_empty(str); // 非法
bool flag = is_empty("hello"); // 非法
```
所以要將這個函式的參數定義為常數參考
```cpp
bool is_empty(const string& s) { return s.empty(); }
```
## 練習 6.17
寫一個函式來判斷一個 string 是否含有任何大寫字母。寫一個函式來將一個 string 全部變為小寫。你在這些函式中使用的參數有相同的型別嗎?若是,為甚麼呢?若非,為何沒有?
解:
兩個函式的參考不一樣。第一個函式使用常數參考,第二個函式使用普通參考
```cpp
// hasUpper
#include <iostream>
#include <string>
using std::cin;
using std::string;
using std::cout using std::boolalpha
bool
hasUpper(const string &s) {
bool check = false;
for (const char c : s) {
check = isupper(c);
if (check)
return check;
}
return check;
}
int main() {
string input;
cout << "Please enter a string:";
cin >> input;
cout << boolalpha << hasUpper(input);
}
```
```cpp
// strToLower
#include <iostream>
#include <string>
using std::cin;
using std::string;
using std::cout using std::boolalpha
std::string &
strToLower(std::string &s) {
for (char &c : s)
c = tolower(c);
return s;
}
int main() {
std::string input;
std::cout << "Please enter a string:";
std::cin >> input;
std::cout << strToLower(input);
}
```
## 練習 6.18
為下列每個函式撰寫宣告。當你編寫這些宣告時,使用函式名稱來表明函式所做的事
- (a)一個名為 compare 的函式,它會回傳一個 bool 並有兩個參數,這兩個參數都是對名為 matrix 的類別的參考
- (b)一個名為 change_val 的函式,它會回傳一個 vector\<int> 迭代器,並接受兩個參數:其一是一個 int,而另一個是某個 vector<int> 的迭代器
解:
```cpp
(a)bool compare(matrix &m1, matrix &m2);
(b)vector<int>::iterator change_val(int num, vector<int>::iterator iter);
```
## 練習 6.19
給定下列宣告,判斷那些呼叫是合法的,而哪些是非法的。對於那些非法的呼叫,請解釋不合法的原因
```cpp
double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);
(a)calc(23.4, 55.1);
(b)count("abcda",'a');
(c)calc(66);
(d)sum(vec.begin(), vec.end(), 3.8);
```
解:
- (a)不合法,calc 只有一個參數
- (b)合法
- (c)合法
- (d)合法
## 練習 6.20
什麼時候參考參數應該是對 const 的參考呢?如果我們在一個參數可以對 const 的參考之時讓它是一個普通的參考,那會發生什麼事呢?
解:
應盡量將參考參數設為常數,除非有明確的目的是為了改變這個參考參數
如果參數應該是常數參考,而我們將其設為了普通參考,那麼常數引數或字面值將無法用於普通參考參數
## 練習 6.21
撰寫一個函式,接受一個 int 和對 int 的一個指標,並回傳所接受的 int 值和指標所指的值之中比較大的那一個。你應該為這個指標使用什麼型別呢?
解:
```cpp
#include <iostream>
using std::cout;
int larger_one(const int i, const int *const p) { return (i > *p) ? i : *p; }
int main() {
int i = 6;
cout << larger_one(7, &i);
return 0;
}
```
應為 const int* const 型別
## 練習 6.22
寫一個函式來對調兩個 int 指標
解:
```cpp
#include <iostream>
#include <string>
using std::cout;
using std::endl;
void swap(int *&n, int *&m) {
int *tmp = n;
n = m;
m = tmp;
}
int main() {
int i = 30, j = 90;
int *n = &i, *m = &j;
swap(n, m);
cout << *n << " " << *m << endl;
return 0;
}
```
## 練習 6.23
為本節所呈現的那些 print 函式寫出你自己的版本。呼叫那些函式中的每一個來印出定義如下的 i 與 j:
```cpp
int i = 0, j[2] = { 0, 1 };
```
解:
```cpp
#include <iostream>
using std::begin;
using std::cout;
using std::end;
using std::endl;
void print(const int *pi) {
if (pi)
cout << *pi << endl;
}
void print(const int *beg, const int *end) {
while (beg != end)
cout << *beg++ << endl;
}
void print(const int ia[], size_t size) {
for (size_t i = 0; i != size; ++i) {
cout << ia[i] << endl;
}
}
void print(int (&arr)[2]) {
for (auto i : arr)
cout << i << endl;
}
int main() {
int i = 0, j[2] = {0, 1};
print(&i);
print(begin(j), end(j));
print(j, end(j) - begin(j));
print(j);
return 0;
}
```
## 練習 6.24
解釋下列函式的行為。如果程式碼中有問題,請解釋問題為何,以及你會如何修正它們
```cpp
void print(const int ia[10]) {
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}
```
解:
當陣列作為引數的時候,會自動轉換為指向第一個元素的指標,因此函式的參數接受的是一個指標,實際上,以陣列作為參數都會被轉為指向元素型別的指標,而函式則會被轉為指向函式的指標
如果要讓這個程式碼可以執行(不更改也可以執行),可將參數改為陣列的參考
```cpp
void print(const int (&ia)[10]) {
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}
```
## 練習 6.25
撰寫接受兩個引數的一個 main 函式。串接所提供的引數,並印出所產生的 string
## 練習 6.26
寫一個程式接受在本節中呈現的那些選項。印出傳入 main 的引數之值
解:
```cpp
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
int main(int argc, char **argv) {
string str;
for (int i = 1; i != argc; ++i)
str += string(argv[i]) + " ";
cout << str << endl;
return 0;
}
```
## 練習 6.27
撰寫一個函式,接受一個 initializer_list\<int> 並產生該串列中元素的總和
解:
```cpp
#include <initializer_list>
#include <iostream>
using std::cout;
using std::endl;
using std::initializer_list;
int sum(initializer_list<int> const &il) {
int sum = 0;
for (auto i : il)
sum += i;
return sum;
}
int main(void) {
auto il = {1, 2, 3, 4, 5, 6, 7, 8, 9};
cout << sum(il) << endl;
return 0;
}
```
## 練習 6.28
在具有 ErrCode 參數的那第二個版本的 error_msg 中,for 迴圈中的 elem 之型別為何?
解:
elem 是 const string& 型別
## 練習 6.29
當你在一個範圍 for 中使用一個 initializer_list,你會使用一個參考作為迴圈的控制變數嗎?若是,為甚麼呢?如果不是,為何不呢?
解:
會使用常數參考型別,因為 initializer_list 物件中的元素都是常數,我們無法修改 initilizer_list 物件中元素的值
## 練習 6.30
編譯前面所呈現的 str_subrange 版本,看看你的編譯器會如何處理我們提過的那些錯誤
解:
```
main.cpp:4:5: error: return-statement with no value, in function returning ‘bool’ [-fpermissive]
```
## 練習 6.31
何時回傳一個參考是有效的呢?那麼對 const 的參考呢?
解:
當回傳的參考引用的物件是區域變數時,回傳的參考會是無效的(也被稱為 dangling),當我們希望回傳的物件被修改時,回傳常數參考是無效的
## 練習 6.32
指出下列的函式是否合法。若是,解釋為何如此;如果不是,更正所有的錯誤並解釋之
```cpp
int &get(int *array, int index) { return array[index]; }
int main() {
int ia[10];
for (int i = 0; i != 10; ++i)
get(ia, i) = i;
}
```
解:
合法,get 函式根據索引取得陣列中的元素的參考
## 練習 6.33
寫一個遞迴函式印出一個 vector 的內容
解:
```cpp
#include <iostream>
#include <vector>
using std::vector;
using std::cout;
using Iter = vector<int>::const_iterator;
void print(Iter first, Iter last)
{
if (first != last)
{
cout << *first << " ";
print(++first, last);
}
}
int main()
{
vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(vec.cbegin(), vec.cend());
return 0;
}
```
## 練習 6.34
如果 factorial 中的停止條件是 if (val != 0) 會發生什麼事?
解:
如果 val 為正數,以結果來看並無區別,只是多乘了 1
如果 val 為負數,那麼遞迴永遠都不會結束
## 練習 6.35
對 factorial 的呼叫中,為甚麼我們傳入 val - 1 而非 val--?
解:
如果傳入的值是 val--,那麼將會永遠傳入相同的值來呼叫該函式,則遞迴永遠都不會結束,可改為 --val
## 練習 6.36
宣告一個會回傳一個參考的函式,該參考指涉有十個 string 的一個陣列,而且不使用尾端回傳、decltype 或型別別名
解:
```cpp
string (&fun())[10];
```
## 練習 6.37
為前一個練習中的函式撰寫三個額外的宣告。其中一個應該使用型別別名,一個使用尾端回傳,而第三個則使用 decltype。你喜歡哪個形式?為甚麼呢?
解:
```cpp
typedef string str_arr[10];
str_arr& fun();
auto fun()->string(&)[10];
string s[10];
decltype(s)& fun();
```
個人認為使用尾端回傳最好,較為簡潔且只需要一行程式碼
## 練習 6.38
修改 arrPtr 函式,改回傳對陣列的一個參考
解:
```cpp
decltype(odd) &arrPtr(int i) { return (i % 2) ? odd : even; }
```
## 練習 6.39
解釋下列每組宣告中第二個宣告的效果。指出何者是非法的,如果有的話
```cpp
(a)int calc(int, int);
int calc(const int, const int);
(b)int get();
double get();
(c)int *reset(int *);
double *reset(double *);
```
解:
- (a)非法,因為頂層 const 不影響傳入函式的物件,所以無法區分第一個宣告與第二個宣告
- (b)非法,對於重載的函式中,它們應該只有參數的數量和參數的型別不同,回傳值與重載無關
- (c)合法
## 練習 6.40
下列哪個宣告是錯的?為甚麼呢?
```cpp
(a)int ff(int a, int b = 0, int c = 0);
(b)char *init(int ht = 24, int wd, char bckgrnd);
```
解:
- (a)正確
- (b)錯誤,一旦某個參數被賦予了默認值,那麼它之後的參數都必須要有默認值
## 練習 6.41
下列哪個呼叫是非法的呢?為甚麼呢?如果有的話,哪個是合法的,但不太可能是程式設計師所期望的?為甚麼呢?
```cpp
char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a)init();
(b)init(24,10);
(c)init(14,'*');
```
解:
- (a)非法,第一個參數沒有默認值,因此最少需要一個引數
- (b)合法
- (c)合法,但使用方式不正確,字元 * 被轉換成 int 傳入到了第二個參數,而不是第三個參數
## 練習 6.42
賦予 make_plural(6.3.2)的第二個參數一個 's' 的預設引數。印出字詞 success 和 failure 的單數與複數版本來測試你的程式
解:
```cpp
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
string make_plural(size_t ctr, const string &word, const string &ending = "s") {
return (ctr > 1) ? word + ending : word;
}
int main() {
cout << "single: " << make_plural(1, "success", "es") << " "
<< make_plural(1, "failure") << endl;
cout << "plural: " << make_plural(2, "success", "es") << " "
<< make_plural(2, "failure") << endl;
return 0;
}
```
## 練習 6.43
你會將下列哪個宣告和定義放在一個標頭中?或放在源碼檔中?請解釋原因
```cpp
(a)inline bool eq(const BigInt&, const BigInt&) {...}
(b)void putValues(int *arr, int size);
```
解:
全部都放進標頭中,因為(a)是 inline 函式,(b)是函式宣告
## 練習 6.44
將 6.2.2 的 isShorter 函式改寫為 inline
解:
```cpp
inline bool is_shorter(const string &str1, const string &str2) {
return str1.size() < str2.size();
}
```
## 練習 6.45
重新檢視你為前面的練習所寫的程式,判斷它們是否應該被定義為 inline。如果是,就那麼做。若非,請解釋它們為何不應該是 inline
解:
一般來說,inline 應用於較簡短、流程直接、頻繁呼叫的函式上
## 練習 6.46
有可能將 isShorter 定義為一個 constexpr 嗎?若是,就那麼做。如果沒辦法,請解釋原因
解:
不能,因為 string.size() 並不是 constexpr 函式,因此 s1.size() == s2.size() 並不是一個 constant expression
## 練習 6.47
修改你在 6.3.2 的練習中寫的那個以遞迴印出一個 vector 內容的程式,讓它能夠條件式地印出其執行相關的資訊。舉例來說,你可能會在每次呼叫印出 vector 的大小,編譯並執行該程式,先開啟除錯來執行,然後關閉除錯再次執行
解:
```cpp
#include<iostream>
#include <vector>
using std::vector;
using std::cout;
using std::endl;
void printVec(vector<int> &vec) {
#ifndef NDEBUG
cout << "Vector size: " << vec.size() << endl;
#endif
if (!vec.empty()) {
auto tmp = vec.back();
vec.pop_back();
printVec(vec);
cout << tmp << " ";
}
}
int main() {
vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
printVec(vec);
cout << endl;
return 0;
}
```
## 練習 6.48
解釋這個迴圈所做的事,以及這是否為 assert 的良好用法:
```cpp
string s;
while (cin >> s && s != sought) { } // 空的主體
assert(cin);
```
解:
不合理,從這個程式的意圖來看,應該改為:
```cpp
assert(s == sought);
```
## 練習 6.49
候選函式是甚麼?合用函式是什麼?
解:
候選函式:與被呼叫的函式同名,並且其宣告在呼叫的地方是可見的
合用函式:參數與引數的數量相等,並且每個引數型別與對應的參數型別相同或者能轉換成參數的型別
## 練習 6.50
給定前面的 f 宣告,為下列每個呼叫列出合用的函式。指出哪個函式是最佳匹配,哪個呼叫因為沒有匹配或歧義而是非法的
```cpp
(a)f(2.56, 42)
(b)f(42)
(c)f(42, 0)
(d)f(2.56, 3.14)
```
解:
- (a)void f(int, int); 和 void f(double, double = 3.14); 都是合用的函式,但呼叫有歧異而不合法
- (b)void f(int); 是合用的函式,且呼叫合法
- (c)void f(int, int); 和 void f(double, double = 3.14); 都是合用的函式,而 void f(int, int); 是最佳匹配
- (d)void f(int, int); 和 void f(double, double = 3.14); 是可行函式,void f(double, double = 3.14); 是最佳匹配
## 練習 6.51
寫出 f 的四個版本。每個函式都應該印出一個足以區別的訊息。檢查你在前一個練習的答案。如果你的答案是錯的,就研讀這一節,直到你了解為何答案是錯的為止
解:
```cpp
#include <iostream>
using std::cout;
using std::endl;
void f()
{
cout << "f()" << endl;
}
void f(int)
{
cout << "f(int)" << endl;
}
void f(int, int)
{
cout << "f(int, int)" << endl;
}
void f(double, double)
{
cout << "f(double, double)" << endl;
}
int main()
{
//f(2.56, 42); // error: 'f' is ambiguous.
f(42);
f(42, 0);
f(2.56, 3.14);
return 0;
}
```
## 練習 6.52
給定下列宣告
```cpp
void manip(int ,int);
double dobj;
```
下列呼叫中每個轉換的排位(6.6.1)為何?
```cpp
(a)manip('a', 'z');
(b)manip(55.4, dobj);
```
解:
- (a)第三級,透過型別提升所達成的匹配
- (b)第四級,透過算術型別轉換所達成的匹配
## 練習 6.53
說明下列宣告組合中第二個宣告的效果。如果有的話,指出哪個是非法的
```cpp
(a)int calc(int&, int&);
int calc(const int&, const int&);
(b)int calc(char*, char*);
int calc(const char*, const char*);
(c)int calc(char*, char*);
int calc(char* const, char* const);
```
解:
- (a):合法,為函式多載,代表的函式是不相同的
- (a):合法,為函式多載,代表的函式是不相同的
- (c):非法,函式參數列中的頂層 const 在不影響函式匹配
## 練習 6.54
為一個函式撰寫宣告,它有兩個 int 參數,並回傳一個 int,然後再宣告一個 vector,其元素具有這種函式指標型別
解:
```cpp
int func(int, int);
vector<decltype(func)*> v;
```
## 練習 6.55
寫出四個函式,為每兩個 int 值進行加、減、乘和除。將這些函式的指標儲存到你為前一個練習所寫的 vector
解:
```cpp
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
v.push_back(add);
v.push_back(subtract);
v.push_back(multiply);
v.push_back(divide);
```
## 練習 6.56
呼叫那個 vector 中的每個元素,並印出它們的結果
解:
```cpp
std::vector<decltype(func) *> vec{add, subtract, multiply, divide};
for (auto f : vec)
std::cout << f(2, 2) << std::endl;
```