Advanced C++: Currying and std::bind
1. Currying Process
1.1. Introduction of operator()
Consider a function that returns a different result on each call. For example, two calls should yield different values.
1.1.1. Simple Approach
Using a global (or static) variable that is modified and returned each time.
#include <iostream>
int counter;
int incrementCounter() {
return ++counter;
}
int main() {
std::cout << "Hello, World!" << std::endl;
std::cout << std::boolalpha << (incrementCounter() == incrementCounter()) << std::endl;
return 0;
}
1.1.2. operator() Overlaoding
Using a class with operator() overloaded (functor).
class Functor {
public:
int x = 0;
int operator()() {
return ++x;
}
};
void test2() {
Functor func;
std::cout << std::boolalpha << (func() == func()) << std::endl;
}
1.2. Chain Adding
Now consider a functon that can be called in a chain: add(1) = 1, add(1)(2) = 3, add(1)(2)(3) = 6, etc. It should also support comparisons like add(1) == 1, and operations like add(1) + 3, add(1) - 3.
- The function
add(1)should return a callable object that can accept further arguments (chainable). - This suggests returning
*thisfromoperator(), enabling chaining. - For comparisons, overload
operator==. - For arithmetic, overload
operator+andoperator-. - For output, overload
<<. - Alternatively, define an implicit conversion to
intto simplify comparisons, arithmetic, and output.
class Functor {
public:
int value;
Functor() : value(0) {}
Functor(int x) : value(x) {}
Functor& operator()(int val) {
value += val;
return *this;
}
bool operator==(int x) const {
return value == x;
}
Functor& operator-(int x) {
value -= x;
return *this;
}
Functor& operator+(int x) {
value += x;
return *this;
}
friend std::ostream& operator<<(std::ostream& out, const Functor& f) {
out << f.value;
return out;
}
// Implicit conversion to int (can replace ==, arithmetic, and <<)
// operator int() const { return value; }
};
int main() {
Functor f1;
f1(1);
std::cout << f1.value << std::endl;
Functor f2;
f2(1)(2);
std::cout << f2.value << std::endl;
Functor f3;
std::cout << std::boolalpha << (f3(1) == 1) << std::endl;
Functor f4(1);
f4 = f4 - 2;
f4 = f4 + 5;
std::cout << f4.value << std::endl;
std::cout << f4 << std::endl;
return 0;
}
This demonstrates operator overloading and object-oriented design in C++.
1.3. Currying Process
The chaining above is a form of currying. This is also seen in lambdas that return lambdas:
// Equivalent: add(1, 2) -> add(1)(2)
void test4() {
auto add = [](int x) {
return [x](int y) {
return x + y;
};
};
std::cout << add(1)(2) << std::endl;
}
2. std::bind
std::bind is used for partial function application (binding arguments).
#include <iostream>
#include <functional>
int add(int a, int b) {
std::cout << "a = " << a << ", b = " << b << std::endl;
return a + b;
}
int main() {
using namespace std::placeholders;
auto f1 = std::bind(add, 1, _1);
std::cout << f1(2) << std::endl; // Calls add(1,2)
auto f2 = std::bind(add, _1, 1);
std::cout << f2(2) << std::endl; // Calls add(2,1)
std::cout << std::bind(add, 1, _1)(2) << std::endl;
std::cout << std::bind(add, _1, _2)(3, 4) << std::endl;
std::cout << std::bind(add, _2, _1)(3, 4) << std::endl;
std::cout << std::bind(add, _1, _1)(3, 4) << std::endl;
std::cout << std::bind(add, _2, _2)(3, 4) << std::endl;
// C++20: std::bind_front
// std::cout << std::bind_front(add, 1)(2) << std::endl;
// C++23: std::bind_back
// std::cout << std::bind_back(add, 2)(1) << std::endl;
return 0;
}

- The placeholder
_irefers to the i-th argument of the resulting callable. - Multiple placeholders can map to the same argument.
std::bindis closely related to perfect forwarding andstd::forward.