Functions in C++
A function is a block of code which only runs when it is called.
You can pass data, known as parameters, into a function.
Functions are used to perform certain actions, and they are important for reusing code: Define the code once, and use it many times.
Operators
An operator is actually a function which uses different syntax. So, instead of add(a,b) after defining function add(...) you would just type a + b, as long as operator + is defined for the types of a and b.
Note that, when calling a binary operator, you write it between its operands (x + y). A unary operator is written before its one operand (as in !married)
C++ divides the operators into the following groups:
- arithmetic operators
- assignment operators
- comparison operators
- logical operators
- bitwise operators
Operator Precedence
(From https://www.w3schools.com/cpp/cpp_operators_precedence.asp)
Here are some common operators in C++, from highest to lowest priority:
(): Parentheses*, /, %: Multiplication, Division, Modulus+, -: Addition, Subtraction>, <, >=, <=: Comparison==, !=: Equality&&: Logical AND||: Logical OR=: Assignment
An Example illustrating that subtraction and addition are done from left to right, unless you add parentheses:
int result1 = 10 - 2 + 5; // (10 - 2) + 5 = 13 int result2 = 10 - (2 + 5); // 10 - 7 = 3 cout << result1 << "\n"; cout << result2 << "\n";
[...]
C++20: The Three-Way Comparison Operator
(June 11, 2020 by Rainer Grimm at https://www.modernescpp.com/index.php/c-20-the-three-way-comparison-operator/)
The three-way comparison operator <=> is often just called the spaceship operator. The spaceship operator determines whether A < B, A = B, or A > B for two values, A and B. You can define the spaceship operator, or the compiler can auto-generate it.
Before C++20 introduced the three-way comparison operator, it was not enough to define operator< but you also needed to define the remaining five in a consistent way, something like this:
bool operator==(const MyInt& rhs) const {
return value == rhs.value;
}
bool operator!=(const MyInt& rhs) const {
return !(*this == rhs);
}
bool operator<=(const MyInt& rhs) const {
return !(rhs < *this);
}
bool operator>(const MyInt& rhs) const {
return rhs < *this;
}
bool operator>=(const MyInt& rhs) const {
return !(*this < rhs);
}
You can define the three-way comparison operator or request it from the compiler with =default. In both cases, you get all six comparison operators: ==, !=, <, <=, >, and >=.
The programmer could define it like this:
struct MyInt {
int value;
explicit MyInt(int val): value{val} { }
auto operator<=>(const MyInt& rhs) const { // (1)
return value <=> rhs.value;
}
};
or just let the compiler define it:
struct MyDouble {
double value;
explicit constexpr MyDouble(double val): value{val} { }
auto operator<=>(const MyDouble&) const = default; // (2)
};
The compiler-generated three-way comparison operator needs the header <compare>, which is implicit constexpr and noexcept. Additionally, it performs a lexicographical comparison.
lambdas
(Largely from https://www.w3schools.com/cpp/cpp_functions_lambda.asp)
A lambda function is a small, anonymous function you can write directly in your code. It's useful when you need a quick function without naming it or declaring it separately.
Syntax
[capture] (parameters) {code};
A Simple Lambda Example
Here, message holds a lambda function that prints a message to the screen:
Example
int main() {
auto message = []() {
cout << "Hello World!\n";
};
message();
return 0;
}
Lambda with Parameters
You can pass values into a lambda just like a regular function:
#include <iostream>
using namespace std;
int main() {
auto add = [](int a, int b) {
return a + b;
};
cout << add(3, 4);
return 0;
}
Passing Lambdas to Functions
You can also pass a lambda function as an argument to another function. This is useful when you want to tell a function what to do, not just what data to use.
In the example below, we send a small lambda function to another function, which then runs it twice:
#include <iostream>
#include <functional> // Needed for std::function
using namespace std;
// A function that takes another function as parameter
void myFunction(std;;function<void()> func) {
func();
func();
}
int main() {
auto message = []() {
cout << "Hello World!\n";
};
myFunction(message);
return 0;
}
Note that you must include the <functional> header for this example to work.
Capture Clause []
You can use the [ ] brackets to give a lambda access to variables outside of it. This is called the capture clause.
In the example below, the lambda captures the variable x by value (a copy):
#include <iostream>
int main() {
int x = 10;
auto show = [x]() {
cout << x;
};
show();
return 0;
}
Note The lambda uses a copy of x. If you change x after defining the lambda, it won't affect the value inside the lambda, but you can also use [&] to capture by reference.
This is an example of capture by reference (the lambda will work with the original variable, not a separate copy):
int main() {
int x = 10;
auto show = [&x]() {
cout << x;
++x;
};
x = 20; // Change x after the lambda is created
show(); // output: 20
// now x==21
return 0;
}
Regular Functions vs Lambda Functions
Both regular functions and lambda functions let you group code and run it later, but they are used in slightly different situations.
Use a regular function when:
- You plan to reuse the function in multiple places
- You want to give the function a clear, meaningful name
- The logic is long or complex
Use a lambda function when:
- You only need the function once
- The code is short and simple
- You want to pass a quick function into another function
Lambda Capture Initializers
This allows creating lambda captures initialized with arbitrary expressions. The name given to the captured value does not need to be related to any variables in the enclosing scopes and introduces a new name inside the lambda body. The initializing expression is evaluated when the lambda is created (not when it is invoked).
int factory(int i) { return i * 10; }
auto f = [x = factory(2)] { return x; }; // returns 20
auto generator = [x = 0] () mutable {
// this would not compile without 'mutable' as we are modifying x on each call
return x++;
};
auto a = generator(); // == 0
auto b = generator(); // == 1
auto c = generator(); // == 2
Because it is now possible to move (or forward) values into a lambda that could previously be only captured by copy or reference we can now capture move-only types in a lambda by value. Note that in the below example the p in the capture-list of task2 on the left-hand-side of = is a new variable private to the lambda body and does not refer to the original p.
auto p = std::make_unique<int>(1);
auto task1 = [=] { *p = 5; }; // ERROR: std::unique_ptr cannot be copied
// vs.
auto task2 = [p = std::move(p)] { *p = 5; }; // OK: p is move-constructed into the closure object
// the original p is empty after task2 is created
Using this reference-captures can have different names than the referenced variable.
auto x = 1;
auto f = [&r = x, x = x * 10] {
++r;
return r + x;
};
f(); // sets x to 2 and returns 12
Return type deduction
Using an auto return type in C++14, the compiler will attempt to deduce the type for you. With lambdas, you can now deduce its return type using auto, which makes returning a deduced reference or rvalue reference possible.
// Deduce return type as `int`.
auto f(int i) {
return i;
}
template <typename T>
auto& f(T& t) {
return t;
}
// Returns a reference to a deduced type.
auto g = [](auto& x) -> auto& { return f(x); };
int y = 123;
int& z = g(y); // reference to `y`
Generic lambda expressions
C++14 allows the auto type-specifier in the parameter list, enabling polymorphic lambdas.
auto identity = [](auto x) { return x; };
int three = identity(3); // == 3
std::string foo = identity("foo"); // == "foo"
Binding Function Arguments with std::bind() (in the <functional> Header)
std::bind() returns a function object based on fn, but with its arguments bound to args.
template < class Fn, class... Args> /* unspecified */ bind (Fn&& fn, Args&&... args); // (1) template <class Ret, class Fn, class... Args> /* unspecified */ bind (Fn&& fn, Args&&... args); // (2)
Each argument may either be bound to a value or be a placeholder (in namespace std::placeholders):
- If bound to a value, calling the returned function object will always use that value as argument.
- If a placeholder, calling the returned function object forwards an argument passed to the call (the one whose order number is specified by the placeholder).
Calling the returned object returns the same type as fn, unless a specific return type is specified as Ret (second prototype) (note that Ret is the only template parameter that cannot be implicitly deduced by the arguments passed to this function).
The type of the returned object has the following properties:
- Its functional call returns the same as fn with its arguments bound to args... (or forwarded, for placeholders).
- For (1), it may have a member variable named
result_type: if Fn is a pointer to function or member function type, it is defined as an alias of its return type. Otherwise, it is defined asFn::result_type, if such a member type exists. - For (2), it has a member variable named
result_type, defined as an alias of Ret. - It is move-constructible and, if the type of all of its arguments are copy-constructible, it is also copy-constructible. Both constructors never throw, provided none of the corresponding constructors of the decay types of Fn and Args... throw.
Parameters
- fn
-
A function object, pointer to function or pointer to member.
fn shall have a decay type which is move-constructible from fn.
- args...
-
List of arguments to bind: either values, or placeholders.
The types in args... shall have decay types which are move-constructible from their respective arguments in args....
If for any argument, its decay type is a reference_wrapper, it bounds to its referenced value instead.
Return Value
A function object that, when called, calls fn with its arguments bound to args.
If fn is a pointer to member, the first argument expected by the returned function is an object of the class *fn is a member (or a reference to it, or a pointer to it).
Example
#include <iostream> // std::cout
#include <functional> // std::bind
// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}
struct MyPair {
double a,b;
double multiply() {return a*b;}
};
int main () {
using namespace std::placeholders; // adds visibility of _1, _2, _3,...
// binding functions:
auto fn_five = std::bind (my_divide,10,2); // returns 10/2
std::cout << fn_five() << '\n'; // 5
auto fn_half = std::bind (my_divide,_1,2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
auto fn_invert = std::bind (my_divide,_2,_1); // returns y/x
std::cout << fn_invert(10,2) << '\n'; // 0.2
auto fn_rounding = std::bind<int> (my_divide,_1,_2); // returns int(x/y)
std::cout << fn_rounding(10,3) << '\n'; // 3
MyPair ten_two {10,2};
// binding members:
auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
std::cout << bound_member_fn(ten_two) << '\n'; // 20
auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
std::cout << bound_member_data() << '\n'; // 10
return 0;
}
Output:
5 5 0.2 3 20 10
Trailing return types
C++11 allows functions and lambdas an alternative syntax for specifying their return types.
int f() {
return 123;
}
// vs.
auto f() -> int {
return 123;
}
auto g = []() -> int {
return 123;
};
This feature is especially useful when certain return types cannot be resolved:
// NOTE: This does not compile!
template <typename T, typename U>
decltype(a + b) add(T a, U b) {
return a + b;
}
// Trailing return types allows this:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
In C++14, decltype(auto) can be used instead.
Ref-qualified member functions
Member functions can now be qualified depending on whether *this is an lvalue or rvalue reference.
struct Bar {
// ...
};
struct Foo {
Bar& getBar() & { return bar; }
const Bar& getBar() const& { return bar; }
Bar&& getBar() && { return std::move(bar); }
const Bar&& getBar() const&& { return std::move(bar); }
private:
Bar bar;
};
Foo foo{};
Bar bar = foo.getBar(); // calls `Bar& getBar() &`
const Foo foo2{};
Bar bar2 = foo2.getBar(); // calls `Bar& Foo::getBar() const&`
Foo{}.getBar(); // calls `Bar&& Foo::getBar() &&`
std::move(foo).getBar(); // calls `Bar&& Foo::getBar() &&`
std::move(foo2).getBar(); // calls `const Bar&& Foo::getBar() const&`