Está en la página 1de 13

Object-Oriented Programming Language

12/10/2014
14.8 Function-Call Operator
Classes that overload the call operator () allow objects of its type to be used as if they were a
function. These objects are called function object or callable object.
Recall what we have learned in Chapter 10, we can pass any kind of callable object to an STL
algorithm. An object or expression is callable if we can apply the call operator ( 1.5.2, p. 23)
to it. That is, if e is a callable expression, we can write e(args) where args is a commaseparated list of zero or more arguments.
There are three kinds of callables in C++: functions and function pointers ( 6.7, p. 247),
lambda expressions ( 10.3.2), and classes that overload the function-call operator (
14.8, p. 571) which well cover now.
Example: write a simple class AbsInt that takes an integer and return its absolute value. For
example, when we do
cout << a(-3);

We will expect the value of 3. We can of course write a simple function to solve the problem:
absolute.cpp
#include <iostream>
using namespace std;
int absoluteValue(int val){
return val<0 ? -val : val;
}
int main(){
int val;
cout << "Enter a value: ";
cin >> val;
cout << "The absolute value of " << val << " is: "
<< absoluteValue(val) << endl;
return 0;
}

Object-Oriented Programming Language


12/10/2014
Alternatively, we can a class that overloads the function-call operator to solve the problem
absoluteClass.cpp
#include <iostream>
using namespace std;
class AbsoluteValue{
public:
int operator () (int val) { return val<0 ? -val : val; }
};
int main(){
AbsoluteValue absoluteValue;
int val;
cout << "Enter a value: ";
cin >> val;
cout << "The absolute value of " << val << " is: "
<< absoluteValue(val) << endl;
return 0;
}

Function-Object Classes with State


Because classes that overload the function-call operator can also store state, they can be
more flexible than ordinary functions. Function-object classes often contain data members
that are used to customize the operations in the call operator.
Let us consider the example we used in Chapter 10.
lambdaCap.cpp
#include
#include
#include
#include

<iostream>
<string>
<vector>
<algorithm>

using namespace std;


int main()
{
vector<string> words = {"fox", "red", "the", "over", "slow",
"jumps", "quick", "turtle"};
int wordSize;
cout << Input the size of the words to be kept: ;
cin >> wordSize;
auto end_part = partition(words.begin(),words.end(),
[wordSize](const string &s){return s.size() >= wordSize;});
words.erase(end_part,words.end());
cout << "The words left are: ";
for (auto e : words)
cout << e << " ";
2

Object-Oriented Programming Language


12/10/2014
cout << endl;
return 0;
}

In this example, we use capture list in lambda expression to do processing that requires more
arguments than the algorithms predicate allows. We can achieve the same goal with functionobject class.
In-class Exercise 14.3: Write a function-object class LongerString to replace lambda
expressions.
Ex14-3.cpp, LongerString.h
#include
#include
#include
#include
#include
#include

<iostream>
<string>
<list>
<vector>
<algorithm>
LongerString.h // function-object class

using namespace std;


int main()
{
vector<string> words = {"fox", "red", "the", "over", "slow",
"jumps", "quick", "turtle"};
int wordSize;
cout << Input the size of the words to be kept: ;
cin >> wordSize;
auto end_part = stable_partition(words.begin(),words.end(),
LongerString(wordSize));
words.erase(end_part,words.end());
cout << "The words left are: ";
for (auto e : words)
cout << e << " ";
cout << endl;
return 0;
}

A:
#ifndef LONGERSTRING_H
#define LONGERSTRING_H
#include <string>
class LongerString
{
public:
LongerString(unsigned i):sz(i){}
3

Object-Oriented Programming Language


12/10/2014
bool operator ()(const std::string& s){return (s.size()
>= sz);}
private:
unsigned sz;
};
#endif // LONGERSTRING_H

Remark:
auto end_part = stable_partition(words.begin(),words.end(),
LongerString(wordSize));

The third argument to stable_partition is a temporary object of type LongerString


that allow us to store the state through the constructor call. In this case, we store the state
that indicates the size of the words to be kept from input.
Function-object classes and associated containers
Function-object classes also enable us to write our own comparison function for the key type
in an associative container. To specify our own operation, we must supply the type of that
operation when we define the type of an associative container.
For example, we can write a compareChar function-object class to compare the char at our
own customized way. Below is a simple example to customize map that organizes char in a
descending order. The function-object class is given as the third type argument in the map
construction.
mapCustom.cpp
#include <iostream>
#include <map>
using namespace std;
class compareChar {
public:
bool operator() (const char& lhs, const char& rhs) const
{return lhs > rhs;}
};
int main ()
{
map<char, int, compareChar> m;
m['a']=10;
m['b']=30;
m['c']=50;
4

Object-Oriented Programming Language


12/10/2014
m['d']=70;
"

cout << "The elements of the map with the customized order are:
<< endl;
for (auto e : m) cout << e.first << " " << e.second << endl;
cout << endl;

return 0;

In-class Exercise 14.4: The following program allows users to enter a few strings and report
them in a descending alphabetic order. The program then uses this information to organize
these strings in an acceding length order. Below is a sample run:

#include <iostream>
#include <string>
#include <set>
using namespace std;
class descendComp {
public:
bool operator() (const string& lhs, const string& rhs) const
{
return lhs > rhs;
}
};
// Your codes (function-class codes)

Object-Oriented Programming Language


12/10/2014
int main()
{
cout << "Enter a few strings: ";
set<string, descendComp> dcoll;
string s;
while (cin >> s)
dcoll.insert(s);
cout << "The strings in a descending alphabetic order are: ";
for (auto e : dcoll) cout << e << " ";
cout << endl;
// Your codes

return 0;
}

Answer:
class lengthComp {
public:
bool operator() (const string& lhs, const string& rhs) const
{
return lhs.size() < rhs.size();
}
};
multiset<string, lengthComp> lcoll (dcoll.begin(),
dcoll.end());
cout << "The strings in an acceding length order are: ";
for (auto e : lcoll) cout << e << " ";
cout << endl;

14.8.2 Library-Defined Function Objects


The function-object classes have often been used with STL containers and algorithms. Thus,
the standard library defines a set of template classes that represent the arithmetic, relational,
and logical operators listed below.

Object-Oriented Programming Language


12/10/2014

These types are defined in the <functional> header. For example, the plus class has a
function-call operator that applies + to a pair of operands; the modulus class defines a call
operator that applies the binary operator; the equal_to class applies == and so on.
Quick Concept Example: what are the outputs?
funObj.cpp
#include <iostream>
#include <functional>
using namespace std;
int main(){
plus<int> intAdd; // function object that can add two int values
negate<int> intNegate; // function object that can negate an int
value
cout << intAdd(10, 20) << endl;
cout << intNegate(intAdd(10, 20)) << endl;
cout << intAdd(10, intNegate(10)) << endl;
return 0;
}

A:
30
-30
0

Using a Library Function Object with the Algorithms and Containers


The function-object classes that represent operators are often used to override the default
operator used by an algorithm or an associative container. As weve seen, by default, the
sorting algorithms use operator <, which ordinarily sorts the sequence into ascending
order.
To sort into descending order, we can pass an object of type greater. That class generates a
call operator that invokes the greater-than operator of the underlying element type.

Object-Oriented Programming Language


12/10/2014
sortFunObj.cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main(){
cout << "Enter a few strings: ";
vector<string> coll;
string s;
while (cin >> s)
coll.push_back(s);
sort(coll.begin(), coll.end(), greater<string>());
cout << "The strings in a descending alphabetic order are: ";
for (auto e : coll) cout << e << " ";
cout << endl;
return 0;
}

We can do the same thing with set:


setFunObj.cpp
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include <functional>
using namespace std;
int main(){
cout << "Enter a few strings: ";
multiset<string, greater<string>> coll;
string s;
while (cin >> s)
coll.insert(s);

cout << "The strings in a descending alphabetic order are: ";


for (auto e : coll) cout << e << " ";
cout << endl;
return 0;

These library defined function objects are thus building blocks of STL (see ppt).
8

Object-Oriented Programming Language


12/10/2014
In-class Exercise 14.5: The algorithm std::transform applies a function object to each
element in a range and copies the return value to the corresponding location of another range.
transform(beg, end, dest, unaryOp)
transform(beg, end, beg2, dest, binaryOp)

The first version applies a unary operation to each element in the input range. The second
applies a binary operation to elements from the two input sequences. Write a program to read
in a few integers from input and output a sequence that negates all the elements and a
sequence that squares all the elements. Below is a sample run:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main(){
cout << "Enter a few integers: ";
vector<int> coll;
int val;
while (cin >> val)
coll.push_back(val);
// your codes transform ...
cout << "negated: ";
for (auto e : coll) cout << e << " ";
cout << endl;
// your codes transform ...
cout << "square: ";
for (auto e : coll) cout << e << " ";
cout << endl;
return 0;
}

A:
transform(coll.begin(), coll.end(), coll.begin(), negate<int>());

Object-Oriented Programming Language


12/10/2014
transform(coll.begin(), coll.end(), coll.begin(), coll.begin(),
multiplies<int>());

Function Adaptor and bind (10.3.4)


We can use special function adaptors, or so-called binders, to combine a callable object
(function, lambda, function object) with other values or usages. The bind function (defined
in functional header) takes a callable object and generates a new callable that adapts
the parameter list of the original object.
The general form of a call to bind is:
auto newCallable = bind(callable, arg_list);

where newCallable is itself a callable object and arg_list is a comma-separated list of


arguments that correspond to the parameters of the callable. That is, when we call
newCallable, newCallable calls callable, passing the arguments in arg_list.
The arguments in arg_list may include names of the form _n, where n is an integer. These
arguments are placeholders representing the parameters of newCallable (with namespace
std::placeholders). The number n is the position of the parameter in newCallable: _1
is the first parameter in newCallable, _2 is the second, and so forth.
As a simple example, well use bind to generate an object that calls a simple function
check_size with a fixed value for its size:
bool check_size(const string &s, string::size_type sz)
{
return s.size() >= sz;
}
auto check6 = bind(check_size, _1, 6);

This call to bind has only one placeholder, which means that check6 takes a single
argument. The placeholder appears first in arg_list, which means that the parameter in
check6 corresponds to the first parameter of check_size. That parameter is a const
string&, which means that the parameter in check6 is also a const string&. Thus, a
call to check6 must pass an argument of type string, which check6 will pass as the first
argument to check_size.
The second argument in arg_list (i.e., the third argument to bind) is the value 6. That
10

Object-Oriented Programming Language


12/10/2014
value is bound to the second parameter of check_size. Whenever we call check6, it will
pass 6 as the second argument to check_size:
string s = "hello";
bool b1 = check6(s); // check6(s) calls check_size(s, 6)

Function adaptors can be used with the STL predefined function object. For example,
auto f = bind(multiplies<int>(), _1, 10);
cout << f(99) << endl; // 990

In the above example, we define a function object that multiplies a first passed argument with
10.
Function adaptor is often used together with STL algorithms to provide a versatile usage. For
example,
bindReplaceif.cpp
#include
#include
#include
#include

<iostream>
<vector>
<algorithm>
<functional>

using namespace std;


using namespace std::placeholders; // _n
int main()
{
vector<int> coll = {1, 3, 70, 4, 5, 70, 6};
replace_if(coll.begin(), coll.end(),
bind(equal_to<int>(),_1, 70),
42);
cout << "new sequence: ";
for (auto e : coll) cout << e << " ";
cout << endl;
return 0;
}

The replace_if replaces the values equal to 70 with 42.

(Quick Concept Check) More bind example: what are the outputs?
bindDivide.cpp
#include <iostream>
11

Object-Oriented Programming Language


12/10/2014
#include <functional>
using namespace std;
using namespace std::placeholders;
double my_divide (double x, double y) {return x/y;}
int main () {
auto fn_five = bind (my_divide,10,2);
cout << fn_five() << endl;
auto fn_half = bind (my_divide,_1,2);
cout << fn_half(10) << endl;
auto fn_invert = bind (my_divide,_2,_1);
cout << fn_invert(10,2) << endl;
}

return 0;

A:
5
5
0.2

In-class Exercise 14.6: Using library function objects and adaptors to remove all the elements
with values between 5 and 8 for the following elements in a vector:
vector<int> coll = {1, 8, 5, 2, 5, 6, 3, 7, 4, 1, 6, 8, 9, 1,
10};

#include
#include
#include
#include

<iostream>
<vector>
<algorithm>
<functional>

using namespace std;


using namespace std::placeholders;
int main()
{
vector<int> coll = {1, 8, 5, 2, 5, 6, 3, 7, 4, 1, 6, 8, 9, 1,
10};
// Your code
12

Object-Oriented Programming Language


12/10/2014
for (auto e : coll)
cout << e << " ";
cout << endl;
return 0;
}

A:
coll.erase(remove_if(coll.begin(),coll.end(),
bind(logical_and<bool>(),
bind(greater_equal<int>(),_1,5),
bind(less_equal<int>(),_1,8))),
coll.end());

13