Functions
Functions
Declaration vs Definition
- A declaration introduces an identifier and describes its type, be it a type, object, or function. A declaration is what the compiler needs to accept references to that identifier.
- A definition actually instantiates/implements this identifier. It's what the linker needs in order to link references to those entities.
- A definition can be used in the place of a declaration
-
exception: but not
friendfunction definitions which are defined in a class body so that they are implicitly assumed to be part of the surrounding scope, see code example below (ie. function name exists in the surrounding scope, but is not visible there until declared).friend function definitions are not declarations, Lip281struct X {
friend void f() { /* friend function can be defined in the class body */ }
X() { f(); } // error: no declaration for f
void g();
void h();
};
void X::g() { return f(); } // error: f hasn’t been declared
void f(); // declares the function defined inside X
void X::h() { return f(); } // ok: declaration for f is now in scope
-
Scope
cppreference:
- scope: Each name that appears in a C++ program is only visible in some possibly discontiguous portion of the source code called its scope.
- block scope:
- The potential scope of a name declared in a block (compound statement) begins at the point of declaration and ends at the end of the block.
- Actual scope is the same as potential scope unless an identical name is declared in a nested block, in which case the potential scope of the name in the nested block is excluded from the actual scope of the name in the enclosing block.
-
Function parameter scope
- begin of scope: The potential scope of a name declared in a function parameter (...) or of a function-local predefined variable begins at the point of declaration.
- end of scope 1: If the enclosing function declarator is not the declarator of a function definition, its potential scope ends at the end of that function declarator.
- end of scope 2: Otherwise, its potential scope ends at the end of the last exception handler of the function-try-block, or at the end of the function body if a function try block was not used.
- begin of scope: The potential scope of a name declared in a function parameter (...) or of a function-local predefined variable begins at the point of declaration.
// example: function parameter scope:
const int n = 3;
int f1(
int n // scope of function parameter n begins,
// scope of global n pauses
// , int y = n // error: default argument references a function parameter
);
Inside class definitions:
- The definitions of the member functions of a class are nested inside the scope of the class itself.
- Hence, if a member function uses a name of a data member, this name is resolved as the data member defined inside the class.
- ie. we do not have to specify the data member's scope via a
className::prefix
- ie. we do not have to specify the data member's scope via a
- Hence, if a member function uses a name of a data member, this name is resolved as the data member defined inside the class.
Inline Functions
- A function specified as
inline(usually) is expanded "in line" at each call.- the word "usually" means that this expansion does not happen sometimes because the compiler can choose to ignore the
inline! (see point below)
- the word "usually" means that this expansion does not happen sometimes because the compiler can choose to ignore the
- inline functions may be defined multiple times in the program (unlike ordinary functions), but all definitions must match
- therefore, inline functions normally are defined in headers
inlineremoves the run-time overhead of using a normal function.- Explanation: Calling a function is likely to be slower than evaluating the equivalent expression.
- On most machines, a function call does a lot of work:
- Registers are saved before the call and restored after the return;
- arguments may be copied; and
- the program branches to a new location.
- On most machines, a function call does a lot of work:
- Explanation: Calling a function is likely to be slower than evaluating the equivalent expression.
- The
inlinespecification is only a request to the compiler.- The compiler may choose to ignore this request.
- In general, the inline mechanism is meant to optimize small, straight-line functions that are called frequently.
- Many compilers will not inline a recursive function.
- A 75-line function will almost surely not be expanded inline.
- 7.3.2: In practice, well-designed C++ programs tend to have lots of small (inline) functions such as
do_display
Parameter List
From cppreference:
Parameter names declared in function declarations are usually for only self-documenting purposes. They are used (but remain optional) in function definitions.
The type of each function parameter in the parameter list is determined according to the following rules:
- First,
decl-specifier-seqand thedeclaratorare combined as in any declaration (→ see "Definitions") to determine the type. - If the type is "array of
T" or "array of unknown bound ofT", it is replaced by the type "pointer toT". - If the type is a "function type
F", it is replaced by the type "pointer toF".
- Top-level cv-qualifiers are dropped from the parameter type
- This adjustment only affects the function type, but doesn't modify the property of the parameter
- eg.
int f(const int p, decltype(p)*);andint f(int, const int*);declare the same function.
Because of these rules, the following function declarations declare exactly the same function:
// cppreference
// rule 2. "array of `T`"
int f(char s[3]);
int f(char[]);
int f(char* s);
int f(char* const);
int f(char* volatile s);
The following declarations also declare exactly the same function:
// cppreference
// rule 3. "function type `F`"
int f(int());
int f(int (*)()); // phth
int f(int (*g)());
Default Arguments
- 6.5.1
- "A default argument is specified as an initializer for a parameter in the parameter list"
- Arguments in the call are resolved by position. The default arguments are used for the trailing (right-most) arguments of a call.
- "if a parameter has a default argument, all the parameters that follow it must also have default arguments"
- general rule: order the parameters so that those least likely to use a default value appear first!
- when re-declaring functions (recall, re-declaring functions is legal):
- each parameter can have its default specified only once
- defaults can be specified only if all parameters to the right already have defaults
- best practice: Default arguments ordinarily should be specified with the function declaration in an appropriate header.
- changing the value of a default argument
- you can assign a new default value to a default argument
- do not try to change a default argument by "hiding" its name in a new scope (see the example in p. 237 and the corresponding explanation on p.238)
- "Function parameters are not allowed in default arguments (...).", cppreference
int f(int a, int b = a); // Error: the parameter a used in a default argument
Return Values
ignoring return values:
- "calling a function and ignoring the return result is very common", stackoverflow
- eg.
printf("hello\n");ignores the return value, stackoverflow
Static Functions
- see "objects.md" → "static members"
- see stackoverflow
- a
staticfunction is- often used as a class member function
- only very rarely used for a free-standing function
- a
Member Functions
- "member function bodies may use other members of their class regardless of where in the class those members appear" (see "compile order" in Classes)
- "code is interpreted as being inside the scope of the class" (ie. eg. no need to use
thisto access members)
Implicit "this" Parameter
- a
total.isbn()call is translated like aSales_data::isbn(&total)call- "the compiler passes the address of
totalto the implicitthisparameter" (as ifSales_data::isbn(Sales_data *const this)were the function definition, seepassing_by_reference.c- Listing 9.6)
- "the compiler passes the address of
const Member Functions
"const member functions cannot change the object on which they are called."
- "The purpose of that
constis to modify the type of the implicitthispointer." (see this)- phth:
thisis aconstpointer, but theconstfollowing the parameter list makes it a pointer toconst(see pointer to const) - "A
constfollowing the parameter list indicates thatthisis a pointer toconst"
- phth:
We can think of the const member
std::string isbn() const { return bookNo; }
as if it were written as
// this code is illegal: we may not explicitly define the this pointer ourselves
std::string Sales_data::isbn(const Sales_data *const this) { return this->bookNo; }
- A "
constfunction", denoted with the keywordconstafter a function declaration, makes it a compiler error for this class function to change a member variable of the class. However, reading of a class variables is okay inside of the function, but writing inside of this function will generate a compiler error., stackoverflow
inline member functions
- member functions ...
- defined inside the class: automatically
inline - defined outside the class: need to specify as
inline - declared inside the class: need to specify as
inline
- defined inside the class: automatically
inlinemember functions should be defined in the same header as the corresponding class definition (for the same reason as described forinlinefunctions)- it is legal to specify
inlineon both the declaration and the definition.- Best practice: specifying
inlineonly on the definition outside the class can make the class easier to read.
- Best practice: specifying
Move-enabled Members
- Overloaded functions that distinguish between moving and copying a parameter typically have one version that takes a
const T&and one that takes aT&&.
// - can pass any object that can be converted to type X to this version
// - copies data from its parameter
void push_back(const X&); // copy: binds to any kind of X
// - can pass only an rvalue that is not const to this version
// - an exact match (and a better match) for nonconst rvalues and will be run when we pass a modifiable rvalue
void push_back(X&&); // move: binds only to modifiable rvalues of type X
// These push_back functions could be eg. members of class StrVec:
class StrVec {
public:
void push_back(const std::string&); // copy the element
void push_back(std::string&&); // move the element
// other members as before
};
// When we call push_back the type of the argument determines whether
// the new element is copied or moved into the container:
StrVec vec; // empty StrVec
string s = "some string or another";
vec.push_back(s); // calls push_back(const string&)
vec.push_back("done"); // calls push_back(string&&)
- Ordinarily, there is no need to define versions of the operation that take a
const X&&or a (plain)X&.- Usually, we pass an rvalue reference when we want to "steal" from the argument. In order to do so, the argument must not be
const. - Similarly, copying from an object should not change the object being copied. As a result, there is usually no need to define a version that take a (plain)
X¶meter.
- Usually, we pass an rvalue reference when we want to "steal" from the argument. In order to do so, the argument must not be
Lambdas
From learn.microsoft:
- In C++11 and later, a lambda expression - often called a lambda - is a convenient way of defining an anonymous function object (a closure) right at the location where it's invoked or passed as an argument to a function. Typically lambdas are used to encapsulate a few lines of code that are passed to algorithms or asynchronous functions.
Passing Call Parameters in and out of Functions
phth:
- "in" parameters are only passed to a function
- "out" parameters are only returned from a function
- "in-out" parameters are passed and also returned from a function
From C++ Core Guidelines:
- F.15: Prefer simple and conventional ways of passing information
- when you use
movesemantics, "measure to ensure that it really is an improvement, and document/comment because the improvement might not be portable" - The following table summarizes the advice in the following Guidelines, F.16-21. (see notes below the table)
- when you use

- F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to
const - F.17: For "in-out" parameters, pass by reference to non-
const - F.18: For "will-move-from" parameters, pass by
X&&andstd::movethe parameter - F.19: For "forward" parameters, pass by
TP&&and onlystd::forwardthe parameter - F.20: For "out" output values, prefer return values to output parameters
- F.21: To return multiple "out" values, prefer returning a
structortuple
Return Value Optimization, RVO
- in the lecture: the same as copy elision
- Youtube: mCoding: Return Value Optimization and Copy Elision in C++
- why-does-c11-have-implicit-moves-for-value-parameters-but-not-for-rvalue-parameters
Return using Move instead of Copy
TODO: Return using Move instead of Copy
Copy Elision
- for
returnstatements: copy elision happens in addition to the "Move instead of Copy Trick" mentioned above
Initialization:
- a copy or move constructor invocation is often optimized away by constructing the object used to initialize right in the target object
- among other things, this happens when copy or move constructors are invoked when functions
returnvalues (see Copy Constructor, "called whenever ...")
- among other things, this happens when copy or move constructors are invoked when functions
- in the following example a compiler will typically construct the
Xfrommake()directly inx; thus eliminating ("eliding") a copy:
X make(Sometype); // create an "X" object named "make", calls the "X(Sometype)" constructor (defined in BS6.1.1)
X x = make(value); // direct initialize "make" AND
// copy initialize "x" (copying is not necessary because "make" was already created in "x")
Avoids Move Constructor Invocations:
- compiler is obliged (by the C++ standard) to eliminate most copies associated with initialization, so move constructors are not invoked
- eliminates (even) the very minor overhead of a move
- cppreference: "When the initializer is a prvalue, the move constructor call is often optimized out (...), see copy elision."
Does not avoid Move-Assignment Operator Invocations:
- The same is usually not possible for assignments.
- ie. it is usually not possible to implicitly eliminate copy or move operations from assignments
Prvalue semantics ("guaranteed copy elision"):
- phth: "prvalues" sind im Prinzip die "rvalues", die bisher besprochen wurden
- Since C++17, a prvalue is not materialized until needed, and then it is constructed directly into the storage of its final destination.
- This sometimes means that even when the language syntax visually suggests a copy/move (e.g. copy initialization), no copy/move is performed
- examples:
// from: https://en.cppreference.com/w/cpp/language/copy_elision
// phth: brackets "[]" by me
// Example 1: Initializing the returned object in a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() // [return type of f is nonreference, ie. a prvalue]
{
return U(); // constructs a [nameless] temporary of type U,
// then [copy] initializes the returned T from the temporary [using T's copy/move constructor]
}
T g() // [return type of g is nonreference, ie. a prvalue]
{
return T(); // constructs the returned T directly; no move
}
// The destructor of the type returned must be accessible at the point of the return statement and non-deleted, even though no T object is destroyed.
// Example 2: In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
// phth:
// - f() (from Example 1) returns a nameless temporary object of nonreference type T, ie a prvalue
// - T() returns a nameless temporary object of nonreference type T, ie a prvalue
T x = T(T(f())); // x is initialized by the result of f() directly; no move
Functions with Varying Parameters
- Sometimes we do not know in advance how many arguments we need to pass to a function
- two primary ways to write a function that takes a varying number of arguments
- if all the arguments have the same type
- pass a library type named
initializer_list
- if the argument types vary
- write a special kind of function, known as a variadic template
-
ellipsis
- special parameter type
- can be used to pass a varying number of arguments
- should be used only in programs that need to interface to C functions
initializer_list
- primary use:
- to write a function that takes a varying number of arguments (all of the same type)
- although you could use a function template with call parameter
int const (&arr)[N](=const int (&arr)[N]) instead, see C-style Array vsinitializer_listand "templates.md" → "Passing Variable Size Arrays"- but this is less convenient because it introduces (1) the nontype template parameter
Nand (2) the type parameter for the array elements (here:int), whereasintializer_list<int>needs only (2) the type parameter for the elements
- but this is less convenient because it introduces (1) the nontype template parameter
- although you could use a function template with call parameter
- "Initializer-list constructors" (BJ5.2.3)
- to write a function that takes a varying number of arguments (all of the same type)
Lippman:
- defined in the
<initializer_list>header - a library type
- a class template
Backing Array
- an
initializer_listobject "represents an array of values of the specified type", (Lip)- Why "array"?
- "An object of type
std::initializer_list<T>is a lightweight proxy object ("Zwischenobjekt") that provides access to an array of objects of typeconst T.", cppreference- "backing array": "An object of type
std::initializer_list<E>is constructed from an initializer list as if the compiler generated (...) a prvalue of type "array ofNconst E", whereNis the number of elements in the initializer list; this is called the initializer list's backing array.", cppreference- "Copying a
std::initializer_listdoes not copy the backing array of the corresponding initializer list.", cppreference
- "Copying a
- "backing array": "An object of type
- "An object of type
- Why "array"?
Example 1: "Each element of the backing array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.", cppreference
// "backing array"
// phth: what WE see
void f(std::initializer_list<double> il);
void g(float x)
{
f({1, x, 3});
}
// phth: what THE COMPILER sees
void g(float x)
{
const double __a[3] = {double{1}, double{x}, double{3}}; // backing array
f(std::initializer_list<double>(__a, __a + 3));
}
Immutability
Lip:
- elements are always
constvalues- there is no way to change the value of an element
Initialization
// Lip
initializer_list<T> lst; // Default initialization; an empty list of elements of type T.
initializer_list<T> lst{a,b,c...};
// lst has as many elements as there are initializers; elements are copies of
// the corresponding initializers. Elements in the list are const.
Copy, Assignment
- "Copying a
std::initializer_listdoes not copy the backing array of the corresponding initializer list.", see "Backing Array"- phth: like a
shared_ptr(whereshared_ptr's "handled resource" corresponds toinitializer_list's "backing array")
- phth: like a
// Lip
// Copying or assigning an initializer_list does not copy the elements
// in the list. After the copy, the original and the copy share the elements.
lst2(lst)
lst2 = lst
Iterators, Capacity
// Lip
// Capacity
lst.size() // Number of elements in the list.
// Iterators
lst.begin() // Returns a pointer to the first and one past the last element in lst.
lst.end()
As Function Parameter
- phth:
initializer_listis often used to write a function that takes a varying number of arguments- phth: this is useful (and often used), e.g. when passing a constructor parameter for a sequential container, see stackoverflow and "Initializer-list constructors" (BJ5.2.3)
- When we pass a sequence of values to an
initializer_listparameter, we must enclose the sequence in curly braces
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
// expected, actual are strings
if (expected != actual)
error_msg({"functionX", expected, actual});
else
error_msg({"functionX", "okay"});
range for
- we can use a range for to process the elements
void error_msg(ErrCode e, initializer_list<string> il)
{
cout << e.msg() << ": ";
for (const auto &elem : il)
cout << elem << " " ;
cout << endl;
}
C-style Array vs initializer_list
From stackoverflow:
Plain and simple: initializer_list isn't a container. It's an immutable view onto externally allocated elements. It's utterly unsuitable for any scenario a container would be useful in - consider the needless indirection (no resizability), the immutability, the idiomacy of its name. On top of that, it has no proper interface.
A situation where both seem adequate is a constructor parameter for a sequence ("Initializer-list constructors" (BJ5.2.3)). If the length is fixed (or template-parametrized), then int const (&arr)[N] is possible, although initializer_list is far simpler and more flexible. After all, that's what it was designed and intended for..
Comments:
- (...) why do you say that
intializer_listis "far simpler and more flexible"? - max66May 16, 2019 at 13:19 - @max66 It's simpler and more flexible as it doesn't require you to think of, or constrain length the same way an array parameter would. - Columbo
May 16, 2019 at 15:38- phth: see example in "As Function Parameter", this is why we are in section "Functions with Varying Parameters" here
Variadic Templates
see "templates.md"
Ellipsis
- special parameter type
- can be used to pass a varying number of arguments
- should be used only in programs that need to interface to C functions
Return
Values are returned in exactly the same way as variables and parameters are initialized:
- The return value is used to initialize a temporary at the call site, and that temporary is the result of the function call.
- if we return by value, the return value is copied to the call site
- if we return by reference, the return value is not copied
Return Statement
- terminates the function that is currently executing
- returns control to the point from which the function was called
2 forms:
return;return expression;
void functions
- a
returnwith no value may be used only in a function that has a return type ofvoid - functions that return
voidare not required to contain areturn- in a
voidfunction, an implicit return takes place after the function's last statement
- in a
- the
return expression;form may be used only to return the result of calling another function that returnsvoid- returning any other expression from a
voidfunction is a compile-time error
- returning any other expression from a
Return Value
- must have
- the same type as the function return type, or
- a type that can be implicitly converted to the function return type
- every non-
voidfunction must return a value - a
returnwith no value may be used only in a function that has a return type ofvoid
Common Errors
// incorrect return values, this code will not compile
bool str_subrange(const string &str1, const string &str2)
{
// same sizes: return normal equality test
if (str1.size() == str2.size())
return str1 == str2; // ok: == returns bool
// find the size of the smaller string; conditional operator, see § 4.7 (p. 151)
auto size = (str1.size() < str2.size())
? str1.size() : str2.size();
// look at each element up to the size of the smaller string
for (decltype(size) i = 0; i != size; ++i) {
if (str1[i] != str2[i])
return; // error #1: no return value; compiler should detect this error
}
// error #2: control might flow off the end of the function without a return
// the compiler might not detect this error -> what happens at run time is undefined
}
Return a Reference
- "Reference Returns Are Lvalues" (p226)
// Return a reference to the shorter of two strings:
// The strings are not copied when the function is called or when the result is returned
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
// "Reference Returns Are Lvalues" (p226) -> if the return type was not const, we
// could ASSIGN TO the result of shorterString() like so:
//shorterString("hi", "bye") = "X"; // error: return value is const
- "Never Return a Reference or Pointer to a Local Object" (Lippman)
// disaster: this function returns a reference to a local object
const string &manip()
{
string ret;
// transform ret in some way
if (!ret.empty())
return ret; // WRONG: returning a reference to a local object!
else
return "Empty"; // WRONG: "Empty" is a local temporary string
}
Return an Array
Lippman:
- a function cannot return an array
- because we cannot copy an array (see "Arrays" → "Copy")
- however, a function can return a pointer or a reference to an array
- see "cpp-pointers-memory.md" → "Returning a Pointer to an Array"
Return a Function
- we can't return a function type but can return a pointer to a function type (see Return a Pointer to Function)
Pointers to Functions
- a pointer that denotes a function rather than an object
- points to a particular type (like any other pointer)
- a function's type is determined by
- its return type and
- the types of its parameters
- note: the function's name is not part of its type
// compares lengths of two strings
bool lengthCompare(const string &, const string &);
Declare
// pf points to a function returning bool that takes two const string references
bool (*pf)(const string &, const string &); // uninitialized
// important: the parentheses around *pf are necessary:
// because the following line declares a function named pf that returns a bool*
bool *pf(const string &, const string &);
Call a Function via a Pointer
- when we use the name of a function as a value, the function is automatically converted to a pointer
// this is the pf declared above
pf = lengthCompare; // pf now points to the function named lengthCompare
pf = &lengthCompare; // equivalent assignment: address-of operator is optional
- to call the function to which the pointer points (no need to dereference the pointer!)
bool b1 = pf("hello", "goodbye"); // calls lengthCompare
bool b2 = (*pf)("hello", "goodbye"); // equivalent call
bool b3 = lengthCompare("hello", "goodbye"); // equivalent call
Most Vexing Parse (MVP) and Function Pointers
- wikipedia
- "In certain situations, the C++ grammar cannot distinguish between the creation of an object parameter and specification of a function's type."
- "In those situations, the compiler is required to interpret the line as a function type specification."
- stackoverflow: What is the purpose of the Most Vexing Parse?
- "everything that can be interpreted as a declaration, will be interpreted as a declaration"
Question: How to understand the following function?
// bar is a function that
// - takes a pointer to a function that returns a Baz
// - and returns a Foo
Foo bar(Baz());
Fully explicit form:
Foo bar(Baz f());
bar is a function that takes a single parameter f, which is a function (taking no arguments) returning Baz.
Without naming the parameter:
Foo bar(Baz ());
The reason bar ends up taking a pointer to a function is that functions cannot be passed by value, so declaring a parameter as a function automatically decays it into a pointer. The above declaration is equivalent to:
Foo bar(Baz (*)());
// or:
Foo bar(Baz (*f)()); // with a named parameter
This is similar to void foo(int [10]) where int [10] also means int * in a parameter list.
Assignment
- when assigning function pointers to other function pointers, there is no implicit conversion between pointers to one function type and pointers to another function type
- as usual, we can assign
nullptror- a zero-valued integer constant expression
string::size_type sumLength(const string&, const string&);
bool cstringCompare(const char*, const char*);
pf = 0; // ok: pf points to no function
pf = sumLength; // error: return type differs
pf = cstringCompare; // error: parameter types differ
pf = lengthCompare; // ok: function and pointer types match exactly
Overloading
The compiler uses the type of the pointer to determine which overloaded function to use
- the type of the pointer must match one of the overloaded functions exactly!
// declare a pointer to an overloaded function
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; // pf1 points to ff(unsigned)
// the type of the pointer must match one of the overloaded functions exactly
void (*pf2)(int) = ff; // error: no ff with a matching parameter list
double (*pf3)(int*) = ff; // error: return type of ff and pf3 don't match
Return a Function
- we can't return a function type but can return a pointer to a function type
Return a Pointer to Function
- we can't return a function type but can return a pointer to a function type
- the easiest way to declare a function that returns a pointer to function is by using a type alias:
// alias declarations for function types
using F = int(int*, int); // F is a function type, not a pointer
using PF = int(*)(int*, int); // PF is a pointer type
PF f1(int); // ok: PF is a pointer to function; f1 returns a pointer to function
F f1(int); // error: F is a function type; f1 can’t return a function
F *f1(int); // ok: explicitly specify that the return type is a pointer to function
- other ways to return a pointer to function:
// declare f1 directly
int (*f1(int))(int*, int); // f1 returns a pointer, pointer points to a function, that function returns an int
// using a trailing return
auto f1(int) -> int (*)(int*, int);
- we can use
decltypeto simplify writing a function pointer return type- remember, since
decltypereturns a function type, not a pointer to function type, we must add a*:
- remember, since
string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);
// depending on the value of its string parameter,
// getFcn returns a pointer to sumLength or to largerLength
decltype(sumLength) *getFcn(const string &);
Pointers to Member Functions
Example
// https://stackoverflow.com/a/1779703
//
// Just like .*, ->* is used with pointers to members.
#include <iostream>
struct foo {
void bar(void) { std::cout << "foo::bar" << std::endl; }
void baz(void) { std::cout << "foo::baz" << std::endl; }
};
int main(void) {
foo *obj = new foo;
void (foo::*ptr)(void);
ptr = &foo::bar;
(obj->*ptr)();
ptr = &foo::baz;
(obj->*ptr)();
return 0;
}
Best practice
From isocpp:
Always use a typedef:
// sample class
class Fred {
public:
int f(char x, float y);
int g(char x, float y);
int h(char x, float y);
int i(char x, float y);
// ...
};
// - FredMemFn is the type name
// - a pointer of that type points to any member of Fred that takes (char,float), such as Fred's f, g, h and i.
typedef int (Fred::*FredMemFn)(char x, float y); // Please do this!
With that you can write:
// declare a member-function pointer
int main()
{
FredMemFn p = &Fred::f;
// ...
}
// declare functions that receive member-function pointers
void userCode(FredMemFn p)
{ /*...*/ }
// declare functions that return member-function pointers
FredMemFn userCode()
{ /*...*/ }
.* vs ->*
Member access operators: cppreference
From isocpp:
- use
.*when the left-hand argument is a reference to an object, and->*when it is a pointer to an object
class Fred { /*...*/ };
// this typedef is explained in section "Pointers to Member Functions"
typedef int (Fred::*FredMemFn)(int i, double d); // use a typedef!!! please!!!
void sample(Fred x, Fred& y, Fred* z, FredMemFn func)
{
x.*func(42, 3.14);
y.*func(42, 3.14);
z->*func(42, 3.14);
}
// https://stackoverflow.com/a/1779703
// "What is ->* operator in C++?"
// Just like .*, ->* is used with pointers to members
#include <iostream>
struct foo {
void bar(void) { std::cout << "foo::bar" << std::endl; }
void baz(void) { std::cout << "foo::baz" << std::endl; }
};
int main(void) {
foo *obj = new foo; // we need an object (or pointer to an object) to call the member function later
void (foo::*ptr)(void); // declare a pointer-to-member-function
ptr = &foo::bar; // assign bar() to the pointer
(obj->*ptr)(); // call the member function "bar" on the object to which "obj" points
ptr = &foo::baz;
(obj->*ptr)();
return 0;
}
// https://stackoverflow.com/a/6586248
// "What are the pointer-to-member operators ->* and .* in C++?"
//we have a class
struct X
{
void f() {}
void g() {}
};
typedef void (X::*pointer)();
//ok, let's take a pointer and assign f to it.
pointer somePointer = &X::f;
//now I want to call somePointer. But for that, I need an object
X x;
//now I call the member function on x like this
(x.*somePointer)(); //will call x.f()
//now, suppose x is not an object but a pointer to object
X* px = new X;
//I want to call the memfun pointer on px. I use ->*
(px ->* somePointer)(); //will call px->f();
Function Objects
- one kind of callable object
Lippman 14.8:
- Function Objects are "objects of classes that define the call operator"
- Such objects "act like functions" because we can call them (see
absObj(i)) - Calling a function object runs its overloaded call operator
- Such objects "act like functions" because we can call them (see
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
};
int i = -42;
absInt absObj; // object that has a function-call operator
int ui = absObj(i); // passes i to absObj.operator()
Usage
- Function-object classes often contain data members that are used to customize the operations in the call operator:
class PrintString {
public:
PrintString(ostream &o = cout, char c = ' '):
os(o), sep(c) { }
void operator()(const string &s) const { os << s << sep; }
private:
ostream &os; // stream on which to write
char sep; // character to print after each output
};
// Then we can
// either use the defaults
// or supply our own values for the separator or output stream
PrintString printer; // uses the defaults; prints to cout
printer(s); // prints s followed by a space on cout
PrintString errors(cerr, '\n');
errors(s); // prints s followed by a newline on cerr
- Function objects are most often used as arguments to the generic algorithms:
for_each(vs.begin(), vs.end(), PrintString(cerr, ’\n’));
Lambdas
- lambdas are function objects
Library-defined Function Objects
- a set of class templates that represent the arithmetic, relational, and logical operators
- each class defines a call operator that applies the named operation
- defined in the
functionalheader
plus<int> intAdd; // function object that can add two int values
negate<int> intNegate; // function object that can negate an int value
// uses intAdd::operator(int, int) to add 10 and 20
int sum = intAdd(10, 20); // equivalent to sum = 30
sum = intNegate(intAdd(10, 20)); // equivalent to sum = -30
// uses intNegate::operator(int) to generate -10 as the second parameter
// to intAdd::operator(int, int)
sum = intAdd(10, intNegate(10)); // sum = 0
#include <iostream>
#include <functional>
int main() {
std::modulus<int> mod;
int a = 10;
int b = 3;
int result = mod(a, b); // equivalent to a % b
std::cout << "The modulus of " << a << " and " << b << " is " << result << std::endl;
return 0;
}
For Overriding the Default Algorithms
- eg. to sort a vector in descending order (instead of ascending order)
// passes a temporary function object that applies the > operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
- unlike their built-in operator counterparts, the library function objects will work for pointers, too
- eg. to sort a vector of pointers based on their addresses in memory
- this is not possible with the
<operator (because comparing two unrelated pointers using<is undefined)
- this is not possible with the
- eg. to sort a vector of pointers based on their addresses in memory
// "<" operator VS. "less<T>" function object
vector<string *> nameTable; // vector of pointers
// error: the pointers in nameTable are unrelated, so < is undefined
sort(nameTable.begin(), nameTable.end(),
[](string *a, string *b) { return a < b; });
// ok: library guarantees that less on pointer types is well defined
sort(nameTable.begin(), nameTable.end(), less<string*>());
- similarly, the associative containers use
less<key_type>to order their elements- as a result, we can
- define a
setof pointers - use a pointer as the key in a
map
- define a
- as a result, we can
Callable Objects
- examples:
- functions
- pointers to functions
- lambdas
- objects created by
bind - classes that overload the function-call operator (Function Objects)
- a callable object has a type
- eg.
- each lambda has its own unique (unnamed) class type
- function and function-pointer types vary by their return type and argument types
- eg.
Call Signature
- specifies
- the type returned by a call to the object
- the argument type(s) that must be passed in the call
- two callable objects with different types may share the same call signature
- a call signature corresponds to a function type
int(int, int)
Different Types Can Have the Same Call Signature
// all of these callables share the same call signature: int(int, int)
// ordinary function
int add(int i, int j) { return i + j; }
// lambda, which generates an unnamed function-object class
auto mod = [](int i, int j) { return i % j; };
// function-object class
struct divide {
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
};
function Class Template
Motivation: Function Table
Example:- build a simple desk calculator
- implement a function table using a
map- use a
stringas the key - use the function that implements each operator as the value
- index the
mapto call a specific operator
- use a
- implement a function table using a
// maps an operator to a pointer to a function taking two int s and returning an int
map<string, int(*)(int,int)> binops;
// ok: add is a pointer to function of the appropriate type
binops.insert({"+", add}); // {"+", add} is a pair
// error: mod is a lambda, and each lambda has its own class type
binops.insert({"%", mod}); // error: mod is not a pointer to function
function
- is a class template
- creates a
functiontype - defined in the
functionalheader - must specify the call signature of the objects that this particular function type can represent:
// to represent callable objects that return an int result and have two int parameters
function<int(int, int)>
// use that type
function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = divide(); // object of a function-object class
function<int(int, int)> f3 = [](int i, int j) // lambda
{ return i * j; };
cout << f1(4,2) << endl; // prints 6
cout << f2(4,2) << endl; // prints 2
cout << f3(4,2) << endl; // prints 8
- this solves the problem in section "Motivation: Function Table"
// table of callable objects corresponding to each binary operator
// all the callables must take two ints and return an int
// an element can be a function pointer, function object, or lambda
map<string, function<int(int, int)>> binops;
map<string, function<int(int, int)>> binops = {
{"+", add}, // function pointer
{"-", std::minus<int>()}, // library function object
{"/", divide()}, // user-defined function object
{"*", [](int i, int j) { return i * j; }}, // unnamed lambda
{"%", mod} }; // named lambda object
// note: underlying callable objects all have different types from one another
// When we index binops, we get a reference to an object (not a copy of an
// object) of type function. The function type OVERLOADS the call operator.
binops["+"](10, 5); // calls add(10, 5)
binops["-"](10, 5); // uses the call operator of the minus<int> object
binops["/"](10, 5); // uses the call operator of the divide object
binops["*"](10, 5); // calls the lambda function object
binops["%"](10, 5); // calls the lambda function object
Overloaded Functions and function
Problem:
- we cannot (directly) store the name of an overloaded function in an object of type
function
// "add" is an overloaded function
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert( {"+", add} ); // error: which add? (-> ambiguous name)
// Option 1:
// ok: store a function pointer
int (*fp)(int,int) = add; // pointer to the version of add that takes two ints
binops.insert( {"+", fp} ); // ok: fp points to the right version of add
// Option 2:
// ok: use a lambda to disambiguate which version of add we want to use
binops.insert( {"+", [](int a, int b) {return add(a, b);} } );