14-Overloaded Operations and Conversions

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

14.1. Basic Concepts

  • Overloaded operators’ names are the keyword operator followed by the symbol for the operator being defined. An overloaded operator has a return type, a parameter list, and a body.
  • An overloaded operator function has the same number of parameters as the operator has operands. A unary operator has one parameter; a binary operator has two. For binary operator, the left-hand operand is passed to the first parameter and the right-hand operand to the second.
  • Except for the overloaded function-call operator, operator(), an overloaded operator may not have default arguments(6.5.1).
  • If an operator function is a member function, the first(left-hand) operand is bound to the this pointer. So a member operator function has one less explicit parameter than the operator has operands.
  • An operator function must either be a member of a class or have at least one parameter of class type, which means that we cannot change the meaning of an operator when applied to operands of built-in type.
  • Table 14.1 shows whether or not an operator may be overloaded. Well cover overloading new and delete in 19.1.1(p. 820). We can overload only existing operators and cannot invent new operator symbols.

  • Four symbols(+, -, *, &) serve as both unary and binary operators. Either or both of these operators can be overloaded. The number of parameters determines which operator is being defined.
  • An overloaded operator has the same precedence and associativity(4.1.2) as the corresponding built-in operator.

Calling an Overloaded Operator Function Directly

  • We can call an overloaded operator function indirectly by using the operator on arguments of the appropriate type or directly in the same way that we call an ordinary function.
// equivalent calls to a nonmember operator function
data1 + data2;          // normal expression
operator+(data1, data2);    // equivalent function call
  • For member operator function, we name an object(or pointer) on which to run the function and use the dot(or arrow) operator to fetch the function we wish to call:
data1 += data2;         // expression-based ''call''
data1.operator+=(data2);    // equivalent call to a member operator function

Some Operators Shouldn’t Be Overloaded

  • A few operators guarantee the order in which operands are evaluated but these guarantees do not apply to overloaded operators because using an overloaded operator is a function call.
    1. The operand-evaluation guarantees of the logical AND, logical OR(4.3), and comma (4.10) operators are not preserved.
    2. Overloaded versions of && or || operators do not preserve short-circuit evaluation properties. Both operands are always evaluated.
  • Another reason not to overload comma or the address-of operator is that unlike other operators, the language defines what the comma and address-of operators mean when applied to objects of class type. Because these operators have built-in meaning, they ordinarily should not be overloaded.
  • Summary: the comma, address-of, logical AND, and logical OR operators should not be overloaded.

Use Definitions That Are Consistent with the Built-in Meaning

  • Operations with a logical mapping to an operator are good candidates for defining as overloaded operators:
    1. If the class does IO, define the shift operators to be consistent with how IO is done for the built-in types.
    2. If the class has an operation to test for equality, define operator== and operator!=.
    3. If the class has a single, natural ordering operation, define operator<. If the class has operator<, it should have all of the relational operators.
    4. The return type of an overloaded operator usually should be compatible with the return from the built-in version of the operator: The logical and relational operators should return bool, the arithmetic operators should return a value of the class type, and assignment and compound assignment should return a reference to the left-hand operand.

Assignment and Compound Assignment Operators

  • After an assignment, the values in the left-hand and right-hand operands should have the same value, and the operator should return a reference to its left-hand operand. Overloaded assignment should generalize the built-in meaning of assignment.
  • If a class has an arithmetic(4.2) or bitwise(4.8) operator, then it is a good idea to provide the corresponding compound-assignment operator.

Choosing Member or Nonmember Implementation

  • Rules in deciding whether to make an operator a member or a nonmember function:
    1. The assignment(=), subscript([]), call(()), and member access arrow(->) operators must be defined as members.
    2. The compound-assignment operators should be members, but not required.
    3. Operators that change the state of their object or that are closely tied to their given type(such as increment, decrement, and dereference) should be members.
    4. Symmetric operators(those that might convert either operand, such as the arithmetic, equality, relational, and bitwise operators) should be defined as nonmember functions.
  • Programmers expect to be able to use symmetric operators in expressions with mixed types. For example, we can add an int and a double. The addition is symmetric because we can use either type as the left-hand or the right-hand operand. If we want to provide mixed-type expressions involving class objects, then the operator must be defined as a nonmember function.
  • When we define an operator as a member function, the left-hand operand must be an object of the class of which that operator is a member.
string s = "world";
string t = s + "!";     // ok: we can add a const char* to a string
string u = "hi" + s;    // ok, but would be an error if + were a member of string
string x = "hi" + "!";  // error: at least one of the operands has a class type
  • Because string defines + as a nonmember function, "hi" + s is equivalent to operator+("hi", s). The only requirements are that at least one of the operands has a class type, and that both operands can be converted to string.

Exercises Section 14.1

Exercise 14.1

In what ways does an overloaded operator differ from a built-in operator? In what ways are overloaded operators the same as the built-in operators?

  • Differ:
    1. We can call an overloaded operator function directly.
    2. An overloaded operator function must either be a member of a class or have at least one parameter of class type.
    3. A few operators guarantee the order in which operands are evaluated. These overloaded versions of these operators do not preserve order of evaluation and/or short-circuit evaluation.
  • Same: An overloaded operator has the same precedence and associativity as the corresponding built-in operator.

Exercise 14.2

Write declarations for the overloaded input, output, addition, and compound-assignment operators for Sales_data.

istream &operator>>(istream &, Sales_data &);
ostream &operator<<(ostream &, const Sales_data &);
Sales_data operator+(const Sales_data &, const Sales_data &);
Sales_data::Sales_data &operator+=(const Sales_data &);

Exercise 14.3

Both string and vector define an overloaded == that can be used to compare objects of those types. Assuming svec1 and svec2 are vectors that hold strings, identify which version of == is applied in each of the following expressions:
(a) “cobble” == “stone”
(b) svec1[0] == svec2[0]
(c) svec1 == svec2
(d) svec1[0] == “stone”

  • Neither.
  • string
  • vector
  • string

Exercise 14.4

Explain how to decide whether the following should be class members:
(a) %
(b) %=
(c) ++
(d) ->
(e) <<
(f) &&
(g) ==
(h)()

  • Symmetric operator: non-member.
  • Changing state of objects: member.
  • Changing state of objects: member.
  • -> must be member.
  • non-member.
  • Symmetric operator: non-member.
  • Symmetric operator: non-member.
  • () must be member.

Exercise 14.5

In exercise 7.40 from 7.5.1(p. 291) you wrote a sketch of one of the following classes. Decide what, if any, overloaded operators your class should provide.
(a) Book
(b) Date
(c) Employee
(d) Vehicle
(e) Object
(f) Tree

  • For Book: >>, <<, ==.

14.2. Input and Output Operators

14.2.1. Overloading the Output Operator <<

  • The first parameter of an output operator is a reference to a nonconst ostream object:
    1. nonconst because writing to the stream changes its state.
    2. reference because we cannot copy an ostream object.
  • The second parameter should be a reference to const of the class type:
    1. reference because avoid copying the argument.
    2. const because printing an object does not change that object.
  • operator<< normally returns its ostream parameter.

The Sales_data Output Operator

ostream &operator<<(ostream &os, const Sales_data &item)
{
    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<
      ' ' << item.avg_price() << '\n';
    return os;
}

Output Operators Usually Do Minimal Formatting

  • Output operators should print the contents of the object, with minimal formatting. They should not print a newline, otherwise users would be unable to print descriptive text along with the object on the same line.

IO Operators Must Be Nonmember Functions

  • Input and output operators must be nonmember functions, otherwise the left-hand operand would have to be an object of our class type:
Sales_data data;
data << cout; // if operator<< is a member of Sales_data
  • IO operators usually need to read or write the nonpublic data members, so they must be declared as friends(7.2.1).

Exercises Section 14.2.1

Exercise 14.6

Define an output operator for your Sales_data class.

ostream &operator<<(ostream &os, const Sales_data &item)
{
    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<
      ' ' << item.avg_price();
    return os;
}

Exercise 14.7

Define an output operator for String class you wrote for the exercises in 13.5(p. 531).

ostream& operator<<(ostream &os, const String &s)
{
    char *c = const_cast<char*>(s.c_str());
    while(*c)
    {
        os << *c++;
    }
    return os;
}

Exercise 14.8

Define an output operator for the class you chose in exercise 7.40 from 7.5.1(p. 291).

ostream& operator<<(ostream& os, const Book& book)
{
    os << book.no_ << " " << book.name_ << " " << book.author_ << " "
      << book.pubdate_;
    return os;
}

14.2.2. Overloading the Input Operator >>

  • The first parameter of an input operator is a reference to the stream from which it is to read, and the second parameter is a reference to the nonconst object into which to read. The operator usually returns a reference to its given stream.

The Sales_data Input Operator

istream& operator>>(istream &is, Sales_data &item)
{
    double price; // no need to initialize; we'll read into price before we use it
    is >> item.bookNo >> item.units_sold >> price;
    if(is) // check that the inputs succeeded
    {
        item.revenue = price * item.units_sold;
    }
    else
    {
        item = Sales_data(); // input failed: give the object the default state
    }
    return is;
}
  • Input operators must deal with the possibility that the input might fail; output operators generally don’t bother.

Errors during Input

  • Errors that can happen in an input operator include the following:
    1. A read operation might fail because the stream contains data of an incorrect type.
    2. Any of the reads could hit end-of-file or some other error on the input stream.

Indicating Errors

  • Some input operators need to do additional data verification. For example, input operator might check that the bookNo we read is in an appropriate format, so the input operator might need to set the streams condition state to indicate failure(8.1.2), even though the actual IO was successful.
  • Usually an input operator should set only the failbit. Setting eofbit imply that the file was exhausted; setting badbit indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

Exercises Section 14.2.2

Exercise 14.9

Define an input operator for your Sales_data class.

istream& operator>>(istream &is, Sales_data &item)
{
    double price; // no need to initialize; we'll read into price before we use it
    is >> item.bookNo >> item.units_sold >> price;
    if(is) // check that the inputs succeeded
    {
        item.revenue = price * item.units_sold;
    }
    else
    {
        item = Sales_data(); // input failed: give the object the default state
    }
    return is;
}

Exercise 14.10

Describe the behavior of the Sales_data input operator if given the following input:
(a) 0-201-99999-9 10 24.95
(b) 10 24.95 0-210-99999-9

  • ok.
  • Illegal input. But 0-210-99999-9 will be converted to a float. Output: 10 24 22.8 0.95

Exercise 14.11

What, if anything, is wrong with the following Sales_data input operator? What would happen if we gave this operator the data in the previous exercise?

istream& operator>>(istream& in, Sales_data& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price;
    s.revenue = s.units_sold * price;
    return in;
}
  • No input error check. Output same as before.

Exercise 14.12

Define an input operator for the class you used in exercise 7.40 from 7.5.1(p. 291). Be sure the operator handles input errors.

  • Yes.

14.3. Arithmetic and Relational Operators

  • We usually define the arithmetic and relational operators as nonmember functions in order to allow conversions for either the left- or right-hand operand(14.1). These operators shouldn’t change the state of either operand, so the parameters are references to const.
  • An arithmetic operator usually generates a new value that is the result of a computation on its two operands. That value is calculated in a local variable. The operation returns a copy of this local as its result.
  • Classes that define both an arithmetic operator and the related compound assignment should implement the arithmetic operator by using the compound assignment.
// assumes that both objects refer to the same book
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs; // copy data members from lhs into sum
    sum += rhs; // add rhs into sum
    return sum;
}

Exercises Section 14.3

Exercise 14.13

Which other arithmetic operators(Table 4.1(p. 139)), if any, do you think Sales_data ought to support? Define any you think the class should include.

  • No others.

Exercise 14.14

Why do you think it is more efficient to define operator+ to call operator+= rather than the other way around?

  • Stackoverflow

Exercise 14.15

Should the class you chose for exercise 7.40 from 7.5.1(p. 291) define any of the arithmetic operators? If so, implement them. If not, explain why not.

  • Omit.

14.3.1. Equality Operators

  • Classes define the equality operator to test whether two objects are equivalent: compare every data member and treat two objects as equal if and only if all the corresponding members are equal.
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() == rhs.isbn() &&
        lhs.units_sold == rhs.units_sold &&
        lhs.revenue == rhs.revenue;
}

bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
    return !(lhs == rhs);
}
  • Design principles:
    1. If a class has an operation to determine whether two objects are equal, it should define that function as operator== rather than as a named function.
    2. If a class defines operator==, that operator should determine whether the given objects contain equivalent data.
    3. The equality operator should be transitive: if a == b and b == c are both true, then a == c should also be true.
    4. If a class defines operator==, it should also define operator!=, and vice versa.
    5. One of the equality or inequality operators should delegate the work to the other.

Exercises Section 14.3.1

Exercise 14.16

Define equality and inequality operators for your StrBlob
(12.1.1, p. 456), StrBlobPtr(12.1.6, p. 474), StrVec(13.5, p. 526), and String(13.5, p. 531) classes.

  • Omit.

Exercise 14.17

Should the class you chose for exercise 7.40 from 7.5.1(p. 291) define the equality operators? If so, implement them. If not, explain why not.

  • Omit.

14.3.2. Relational Operators

  • Classes for which the equality operator is defined also often have relational operators. Because the associative containers and some of the algorithms use the less-than operator, it can be useful to define an operator<.
  • The relational operators should
    1. Define an ordering relation that is consistent with the requirements for use as a key to an associative container(11.2.2); and
    2. Define a relation that is consistent with == if the class has both operators. If two objects are !=, then one object should be < the other.

Exercises Section 14.3.2

Exercise 14.18

Define relational operators for your StrBlob, StrBlobPtr, StrVec, and String classes.

  • Omit.

Exercise 14.19

Should the class you chose for exercise 7.40 from 7.5.1(p. 291) define the relational operators? If so, implement them. If not, explain why not.

  • Omit.

14.4. Assignment Operators

  • In addition to the copy- and move-assignment operators that assign one object of the class type to another object of the same type(13.1.2 and 13.6.2), a class can define additional assignment operators that allow other types as the right-hand operand.
  • We can add assignment operator that takes a braced list of elements(9.2.5) to StrVec class(13.5) as well.
class StrVec
{
public:
    StrVec &operator=(std::initializer_list<std::string>);
    // other members as in § 13.5 (p. 526)
};

StrVec &StrVec::operator=(initializer_list<string> il)
{
    // alloc_n_copy allocates space and copies elements from the given range
    auto data = alloc_n_copy(il.begin(), il.end());
    free(); // destroy the elements in this object and free the space
    elements = data.first; // update data members to point to the new space
    first_free = cap = data.second;
    return *this;
}

Compound-Assignment Operators

  • Compound assignment operators are not required to be members. But we prefer to define all assignments, including compound assignments, in the class. These operators should return a reference to their left-hand operand.
// member binary operator: left-hand operand is bound to the implicit this pointer
// assumes that both objects refer to the same book
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}
  • Assignment operators must, and compound-assignment operators should, be defined as members. These operators should return a reference to the left-hand operand.

Exercises Section 14.4

Exercise 14.20

Define the addition and compound-assignment operators for your Sales_data class.

  • Same as before.

Exercise 14.21

Write the Sales_data operators so that + does the actual addition and += calls +. Discuss the disadvantages of this approach compared to the way these operators were defined in 14.3(p. 560) and 14.4(p. 564).

Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
    Sales_data old_data = *this;
    *this = old_data + rhs;
    return *this;
}
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum;
    sum.units_sold = lhs.units_sold + rhs.units_sold;
    sum.revenue = lhs.revenue + rhs.revenue;
    return sum;
}
  • Disadvantages: Both + and += uses an temporary object of Sales_data. But it is no need for that.

Exercise 14.22

Define a version of the assignment operator that can assign a string representing an ISBN to a Sales_data.

  • Omit.

Exercise 14.23

Define an initializer_list assignment operator for your version of the StrVec class.

  • Omit.

Exercise 14.24

Decide whether the class you used in exercise 7.40 from 7.5.1(p. 291) needs a copy- and move-assignment operator. If so, define those operators.

  • Omit.

Exercise 14.25

Implement any other assignment operators your class should define. Explain which types should be used as operands and why.

  • Omit.

14.5. Subscript Operator

  • Classes that represent containers from which elements can be retrieved by position often define the subscript operator, operator[].
  • The subscript operator must be a member function and it returns a reference to the element that is fetched. It usually should define two versions: one that returns a plain reference and the other that is a const member and returns a reference to const.
class StrVec
{
public:
    std::string& operator[](std::size_t n)
    {
        return elements[n];
    }
    const std::string& operator[](std::size_t n) const
    {
        return elements[n];
    }
    // other members as in § 13.5 (p. 526)
private:
    std::string *elements; // pointer to the first element in the array
};
  • Because subscript returns a reference to an element, if the StrVec is nonconst, we can assign to that element; if we subscript a const object, we can’t:
// assume svec is a StrVec
const StrVec cvec = svec; // copy elements from svec into cvec
// if svec has any elements, run the string empty function on the first one
if (svec.size() && svec[0].empty())
{
    svec[0] = "zero"; // ok: subscript returns a reference to a string
    cvec[0] = "Zip"; // error: subscripting cvec returns a reference to const
}

Exercises Section 14.5

Exercise 14.26

Define subscript operators for your StrVec, String, StrBlob, and StrBlobPtr classes.

  • Omit.

14.6. Increment and Decrement Operators

  • Classes that define increment or decrement operators should define both the prefix and postfix versions. These operators usually should be defined as members since they change the state of the object on which they operate.

Defining Prefix Increment/Decrement Operators

  • The prefix operators should return a reference to the incremented or decremented object.
class StrBlobPtr
{
public:
    // increment and decrement
    StrBlobPtr& operator++(); // prefix operators
    StrBlobPtr& operator--();
    // other members as before
};
// prefix: return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++()
{
    // if curr already points past the end of the container, can't increment it
    check(curr, "increment past end of StrBlobPtr");
    ++curr; // advance the current state
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
    // if curr is zero, decrementing it will yield an invalid subscript
    --curr; // move the current state back one element
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}
// check returns a shared_ptr to the vector if the check succeeds
shared_ptr<vector<string>> check(size_t i, const string &msg) const
{
    shared_ptr<vector<string>> ret = wptr.lock();
    if(!ret)
    {
        throw runtime_error("unbound StrBlobPtr");
    }
    if(i >= ret->size())
    {
        throw out_of_range(msg);
    }
    return ret;
}
  • Operators call check to verify that the StrBlobPtr is still valid. If so, check then verifies that its given index is valid. If check doesn’t throw an exception, these operators return a reference to this object.

Differentiating Prefix and Postfix Operators

  • To overload prefix and postfix operators, the postfix versions take an extra(unused) parameter of type int. When we use a postfix operator, the compiler supplies 0 as the argument for this parameter. Though the postfix function can use this extra parameter, it should not since its sole purpose is to distinguish a postfix function from the prefix version.
  • Each of operators calls its own prefix version to do the actual work.
    The postfix operators should return the old value as a value, not a reference.
    The int parameter is not used, so we do not give it a name.
class StrBlobPtr
{
public:
    // increment and decrement
    StrBlobPtr operator++(int); // postfix operators
    StrBlobPtr operator--(int);
    // other members as before
};

// postfix: increment/decrement the object but return the unchanged value
StrBlobPtr StrBlobPtr::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check
    StrBlobPtr ret = *this; // save the current value
    ++*this; // advance one element; prefix ++ checks the increment
    return ret; // return the saved state
}

StrBlobPtr StrBlobPtr::operator--(int)
{
    // no check needed here; the call to prefix decrement will do the check
    StrBlobPtr ret = *this; // save the current value
    --*this; // move backward one element; prefix -- checks the decrement
    return ret; // return the saved state
}

Calling the Postfix Operators Explicitly

  • If we want to call the postfix version using a function call, we must pass a value for the integer argument. The value passed is ignored but is necessary in order to tell the compiler to use the postfix version.
StrBlobPtr p(a1);       // p points to the vector inside a1
p.operator++(0);        // call postfix operator++
p.operator++();     // call prefix operator++

Exercises Section 14.6

Exercise 14.27

Add increment and decrement operators to your StrBlobPtr class.

  • Same as before.

Exercise 14.28

Define addition and subtraction for StrBlobPtr so that these operators implement pointer arithmetic(3.5.3, p. 119).

  • Omit.

Exercise 14.29

We did not define a const version of the increment and decrement operators. Why not?

  • Because ++ and – change the state of the object.

14.7. Member Access Operators

  • Operator arrow must be a member. The dereference operator is not required to be a member but usually should be a member as well.
class StrBlobPtr
{
public:
    std::string& operator*() const
    {
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points
    }
    std::string* operator->() const
    {
        // delegate the real work to the dereference operator
        return & this->operator*();
    }
    // other members as before
};
  • The dereference operator checks that curr is still in range and, if so, returns a reference to the element denoted by curr.
    The arrow operator calls the dereference operator and returning the address of the element returned by that operator.
  • Note:
    1. We define these operators as const members since fetching an element doesn’t change the state of a StrBlobPtr.
    2. These operators return a reference or pointer to nonconst string because we know that a StrBlobPtr can only be bound to a nonconst StrBlob(12.1.6).
StrBlob a1 = {"hi", "bye", "now"};
StrBlobPtr p(a1);               // p points to the vector inside a1
*p = "okay";                    // assigns to the first element in a1
cout << p->size() << endl;  // prints 4, the size of the first element in a1
cout << (*p).size() << endl;    // equivalent to p->size()

Constraints on the Return from Operator Arrow

  • When we write point->mem, point must be a pointer to a class object or an object of a class with an overloaded operator->. point->mem is equivalent to
(*point).mem;           // point is a built-in pointer type
point.operator()->mem;  // point is an object of class type
  • point->mem executes as follows:
    1. If point is a pointer, then the built-in arrow operator is applied, which means this expression is a synonym for(*point).mem. The pointer is dereferenced and the indicated member is fetched from the resulting object. If the type pointed to by point does not have a member named mem, then the code is in error.
    2. If point is an object of a class that defines operator->, then the result of point.operator->() is used to fetch mem.
      -1- If that result is a pointer, then step 1 is executed on that pointer.
      -2- If the result is an object that itself has an overloaded operator->(), then this step is repeated on that object. This process continues until either a pointer to an object with the indicated member is returned or some other value is returned, in which case the code is in error.
  • The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.

Exercises Section 14.7

Exercise 14.30

Add dereference and arrow operators to your StrBlobPtr class and to the ConstStrBlobPtr class that you defined in exercise 12.22 from 12.1.6(p. 476). Note that the operators in constStrBlobPtr must return const references because the data member in constStrBlobPtr points to a const vector.

  • Omit.

Exercise 14.31

Our StrBlobPtr class does not define the copy constructor, assignment operator, or a destructor. Why is that okay?

  • Applying the Rule of 3/5: There is no dynamic allocation to deal with, so the synthesized destructor is enough. Moreover, no unique is needed. Hence, the synthesized ones can handle all the corresponding operations.

Exercise 14.32

Define a class that holds a pointer to a StrBlobPtr. Define the overloaded arrow operator for that class.

  • omit.

14.8. Function-Call Operator

  • Objects of classes that define the call operator are referred to as function objects. The function-call operator must be a member function. A class may define multiple versions of the call operator, each of which must differ as to the number or types of their parameters.
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()

Function-Object Classes with State

  • Function-object classes often contain data members that are used to customize the operations in the call operator. E.g., we define a class that prints a string argument. By default, our class will write to cout and print a space following each string. We let users provide a different stream on which to write and provide a different separator.
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
};
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 often used as arguments to the generic algorithms. For example, we can use the library for_each algorithm(10.3.2) and our PrintString class to print the contents of a container:
    for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
  • The third argument is a temporary object of type PrintString that we initialize from cerr and a newline character. The call to for_each will print each element in vs to cerr followed by a newline.

Exercises Section 14.8

Exercise 14.33

How many operands may an overloaded function-call operator take?

  • An overloaded operator function has the same number of parameters as the operator has operands. Hence the maximum value should be around 256. SO

Exercise 14.34

Define a function-object class to perform an if-then-else operation: The call operator for this class should take three parameters. It should test its first parameter and if that test succeeds, it should return its second parameter; otherwise, it should return its third parameter.

#include <iostream>

using namespace std;

struct Choose
{
    int operator()(int a, int b, int c)
    {
        return a != 0 ? b : c;
    }
};

int main()
{
    Choose obj;
    cout << obj(1, 2, 3) << '\n';   // 2
    cout << obj(0, -1, 1);          // 1

    return 0;
}

Exercise 14.35

Write a class like PrintString that reads a line of input from an istream and returns a string representing what was read. If the read fails, return the empty string.

#include <iostream>

using namespace std;

struct Print
{
    string operator()(istream &is = cin)
    {
        string ret;
        if(is >> ret)
        {
            return ret;
        }
        return string();
    }
};

int main()
{
    Print obj;
    cout << obj();

    return 0;
}

Exercise 14.36

Use the class from the previous exercise to read the standard input, storing each line as an element in a vector.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct Print
{
    string operator()(istream &is = cin)
    {
        string ret;
        getline(is, ret);
        return is ? ret : string();
    }
};

int main()
{
    Print obj;
    string str = obj(cin);
    vector<string> vec;
    while(!str.empty())
    {
        vec.push_back(str);
        str = obj(cin);
    }
    for(auto s : vec)
    {
        cout << s << '\n';
    }

    return 0;
}

Exercise 14.37

Write a class that tests whether two values are equal. Use that object and the library algorithms to write a program to replace all instances of a given value in a sequence.

14.8.1. Lambdas Are Function Objects

14.8.2. Library-Defined Function Objects

  • The standard library defines a set of classes that represent the arithmetic, relational, and logical operators. Each class defines a call operator that applies the named operation.
  • These classes are templates to which we supply a single type. That type specifies the parameter type for the call operator. For example, plus applies the string addition operator to string objects; for plus the operands are ints.
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
  • These types, listed in Table 14.2, are defined in the functional header.

Using a Library Function Object with the Algorithms

  • The function-object classes that represent operators are often used to override the default operator used by an algorithm. By default, the sorting algorithms use <, which 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. For example, if svec is a vector,
// passes a temporary function object that applies the < operator to two strings
sort(svec.begin(), svec.end(), greater<string>());
  • The third argument is an unnamed object of type greater. When sort compares elements, it will call the given greater function object which applies > to the string elements.
  • The library guarantees that these library function objects will work for pointers. Comparing two unrelated pointers is undefined(3.5.3), but we might want to sort a vector of pointers based on their addresses in memory. It is undefined to do so directly, but we can do so through one of the library function objects:
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*>());
  • The associative containers use less to order their elements. So, we can define a set of pointers or use a pointer as the key in a map without specifying less directly.

Exercises Section 14.8.2

Exercise 14.42

Using library function objects and adaptors, define an expression to
(a) Count the number of values that are greater than 1024
(b) Find the first string that is not equal to pooh
(c) Multiply all values by 2

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>

int main()
{
    using std::placeholders::_1;

    std::vector<int> ivec { 1, 111, 1111, 11111 };
    int count = std::count_if (ivec.cbegin(), ivec.cend(), std::bind(std::greater<int>(), _1, 1024));
    std::cout << count << std::endl;

    std::vector<std::string> svec { "pooh", "pooh", "pezy", "pooh" };
    auto found = std::find_if (svec.cbegin(), svec.cend(), std::bind(std::not_equal_to<std::string>(), _1, "pooh"));
    std::cout << *found << std::endl;

    std::transform(ivec.begin(), ivec.end(), ivec.begin(), std::bind(std::multiplies<int>(), _1, 2));
    for (int i : ivec) std::cout << i << " ";
    std::cout << std::endl;
}

Exercise 14.43

Using library function objects, determine whether a given int value is divisible by any element in a container of ints.

#include <iostream>
#include <string>
#include <functional>
#include <algorithm>

int main()
{
    auto data = { 2, 3, 4, 5 };
    int input;
    std::cin >> input;
    std::modulus<int> mod;
    auto predicator = [&](int i)
    {
        return 0 == mod(input, i);
    };
    auto is_divisible = std::any_of(data.begin(), data.end(), predicator);
    std::cout << (is_divisible ? "Yes!" : "No!") << std::endl;

    return 0;
}

14.8.3. Callable Objects and function

  • C++ has several kinds of callable objects: functions, pointers to functions, lambdas(10.3.2), objects created by bind(10.3.4), and classes that overload the function-call operator.
  • A callable object has a type. Two callable objects with different types may share the same call signature. The call signature specifies the type returned by a call to the object and the argument type(s) that must be passed in the call. A call signature corresponds to a function type. For example: int(int, int) is a function type that takes two ints and returns an int.

Different Types Can Have the Same Call Signature

  • We can treat several callable objects that share a call signature as if they had the same type. Consider the following different types of callable objects:
// 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 div
{
    int operator()(int denominator, int divisor)
    {
        return denominator / divisor;
    }
};
  • Each of these callables applies an arithmetic operation to its parameters. Even though each has a distinct type, they all share the same call signature: int(int, int)
  • We want to use these callables to build a desk calculator. To do so, we’d want to define a function table to store pointers to these callables. When the program needs to execute a particular operation, it will look in the table to find which function to call.
  • Function tables can be implemented using a map: use a string corresponding to an operator symbol as the key; the value is the function that implements that operator. When we want to evaluate a given operator, we’ll index the map with that operator and call the resulting element.
// maps an operator to a pointer to a function taking two ints and returning an int map<string, int(*)(int,int)> binops;
  • We could put a pointer to add into binops as follows:
    // ok: add is a pointer to function of the appropriate type
    binops.insert({“+”, add}); // {“+”, add} is a pair 11.2.3
 - But we can’t store mod or div in binops:
```cpp
binops.insert({"%", mod}); // error: mod is not a pointer to function




<div class="se-preview-section-delimiter"></div>
  • The problem is that mod is a lambda, and each lambda has its own class type. That type does not match the type of the values stored in binops.

The Library function Type

  • We can solve this problem using C++11 library type function that is defined in . Table 14.3 lists the operations defined by function.

  • function is a template: we must specify the call signature of the objects that this particular function type can represent when we create a function type.
  • function<int(int, int)>
    We declare a function type that can represent callable objects that return an int result and have two int parameters. We can use that type to represent any of our desk calculator types:
function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = div(); // 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




<div class="se-preview-section-delimiter"></div>
  • We can redefine our map using this function type:
// 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 =
{
    {"+", add},                         // function pointer
    {"-", std::minus<int>()},               // library function object
    {"/", div()},                           // user-defined function object
    {"*", [](int i, int j) { return i * j; }},  // unnamed lambda
    {"%", mod}                          // named lambda object
};




<div class="se-preview-section-delimiter"></div>
  • When we index binops, we get a reference to an object of type function. The function type overloads the call operator. That call operator takes its own arguments and passes them along to its stored callable object:
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 div object
binops["*"](10, 5); // calls the lambda function object
binops["%"](10, 5); // calls the lambda function object




<div class="se-preview-section-delimiter"></div>

Overloaded Functions and function

  • We cannot directly store the name of an overloaded function in an object of type 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?




<div class="se-preview-section-delimiter"></div>
  • One solution is to store a function pointer(6.7) instead of the name of the function:
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




<div class="se-preview-section-delimiter"></div>
  • Another is to use a lambda:
// ok: use a lambda to disambiguate which version of add we want to use
binops.insert({"+", [](int a, int b) {return add(a, b);} } );




<div class="se-preview-section-delimiter"></div>
  • The call inside the lambda body passes two ints. That call can match only the version of add that takes two ints, and so that is the function that is called when the lambda is executed.

Exercises Section 14.8.3

Exercise 14.44

Write your own version of a simple desk calculator that can handle binary operations.





<div class="se-preview-section-delimiter"></div>

#include <iostream>




<div class="se-preview-section-delimiter"></div>

#include <string>




<div class="se-preview-section-delimiter"></div>

#include <map>




<div class="se-preview-section-delimiter"></div>

#include <functional>

using namespace std;

int add(int i, int j)
{
    return i + j;
}

auto mod = [](int i, int j)
{
    return i % j;
};

struct Div
{
    int operator ()(int i, int j) const
    {
        return i / j;
    }
};

map<string, function<int(int, int)>> binops =
{
    { "+", add }, // function pointer
    { "-", minus<int>() }, // library functor
    { "/", Div() }, // user-defined functor
    { "*", [](int i, int j) { return i*j; }}, // unnamed lambda
    { "%", mod } // named lambda object
};

int main()
{
    while(1)
    {
        cout << "Input: operand1 operator operand2\n";
        int lhs, rhs;
        string op;
        cin >> lhs >> op >> rhs;
        cout << binops[op](lhs, rhs) << endl;
    }

    return 0;
}




<div class="se-preview-section-delimiter"></div>

14.9. Overloading, Conversions, and Operators

  • Converting constructors and conversion operators define class-type conversions. Such conversions are also called user-defined conversions.

14.9.1. Conversion Operators

  • A conversion function converts a value of a class type to a value of some other type; it must be a member function, may not specify a return type, must have an empty parameter list and should be const. General form: operator type() const;.
  • Conversion operators can be defined for any type(other than void) that can be a function return type. Conversions to an array or a function type are not permitted. Conversions to pointer types(both data and function pointers) and to reference types are allowed.

Defining a Class with a Conversion Operator

  • We define a small class that represents an integer in the range of 0 to 255:
class SmallInt
{
public:
    SmallInt(int i = 0): val(i)
    {
        if (i < 0 || i > 255)
            throw std::out_of_range("Bad SmallInt value"); // <stdexcept>
    }
    operator int() const
    {
        return val;
    }

private:
    std::size_t val;
};




<div class="se-preview-section-delimiter"></div>
  • The constructor converts values of arithmetic type to a SmallInt. The conversion operator converts SmallInt objects to int:
SmallInt si;
si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator=
si + 3; // implicitly converts si to int followed by integer addition




<div class="se-preview-section-delimiter"></div>
  • Though the compiler applies only one user-defined conversion at a time(4.11.2), an implicit user-defined conversion can be preceded or followed by a standard(built-in) conversion(4.11.1). So, we can
    1. Pass any arithmetic type to the SmallInt constructor.
    2. Use the conversion operator to convert a SmallInt to an int and then convert the resulting int value to another arithmetic type.
// the double argument is converted to int using the built-in conversion
SmallInt si = 3.14; // calls the SmallInt(int) constructor
// the SmallInt conversion operator converts si to int;
si + 3.14;          // that int is converted to double using the built-in conversion




<div class="se-preview-section-delimiter"></div>
  • Although a conversion function does not specify a return type, each conversion function must return a value of its corresponding type:
class SmallInt;
operator int(SmallInt&);            // error: nonmember
class SmallInt
{
public:
    int operator int() const;       // error: return type
    operator int(int = 0) const;    // error: parameter list
    operator int*() const
    {
        return 42;              // error: 42 is not a pointer
    }
};




<div class="se-preview-section-delimiter"></div>

Conversion Operators Can Yield Surprising Results

  • Before C++11, classes that wanted to define a conversion to bool faced a problem: since bool is an arithmetic type, a class-type object that is converted to bool can be used in any context where an arithmetic type is expected. Such conversions can happen in surprising ways. E.g., if istream had a conversion to bool, the following code would compile:
int i = 42;
cin << i; // legal if the conversion to bool were not explicit!




<div class="se-preview-section-delimiter"></div>
  • There is no << defined for istream, but it could use the bool conversion operator to convert cin to bool. The resulting bool value would then be promoted to int and used as the left-hand operand to the built-in version of the left-shift operator. The promoted bool value(1/0) would be shifted left 42 positions.

explicit Conversion Operators

  • C++11 introduced explicit conversion operators.
class SmallInt
{
public:
    // the compiler won't automatically apply this conversion
    explicit operator int() const
    {
        return val;
    }
    // other members as before
};




<div class="se-preview-section-delimiter"></div>
  • As with an explicit constructor(7.5.4), the compiler won’t use an explicit conversion operator for implicit conversions.
SmallInt si = 3;            // ok: the SmallInt constructor is not explicit
si + 3;                 // error: operator int is explicit
static_cast<int>(si) + 3;   // ok: explicitly request the conversion




<div class="se-preview-section-delimiter"></div>
  • If the conversion operator is explicit, we must do conversion explicitly through a cast with one exception. The exception is that the compiler will apply an explicit conversion to an expression used as a condition. That is, an explicit conversion will be used implicitly to convert an expression used as
    1. The condition of an if, while, or do statement.
    2. The condition expression in a for statement header.
    3. An operand to the logical NOT(!), OR(||), or AND(&&) operators.
    4. The condition expression in a conditional(?:) operator.

Conversion to bool

  • Under C++11, the IO library defines an explicit conversion to bool. Whenever we use a stream object in a condition, we use the operator bool that is defined for the IO types.
  • while(std::cin >> value);
    The condition in the while executes the input operator, which reads into value and returns cin. To evaluate the condition, cin is implicitly converted by the istream operator bool conversion function. That function returns true if the condition state of cin is good(8.1.2), and false otherwise.

Exercises Section 14.9.1

Exercise 14.45

Write conversion operators to convert a Sales_data to string and to double. What values do you think these operators should return?

explicit operator string() const
{
    return bookNo;
}
explicit operator double() const
{
    return avg_price();
}




<div class="se-preview-section-delimiter"></div>

Exercise 14.46

Explain whether defining these Sales_data conversion operators is a good idea and whether they should be explicit.

  • It’s a bad idea to do so because conversion is misleading.
  • explicit should be added to prevent implicit conversion.

Exercise 14.47

Explain the difference between these two conversion operators:

struct Integral
{
    operator const int();
    operator int() const;
};




<div class="se-preview-section-delimiter"></div>
  • Ignored by compiler.
  • Guarantee that this operator will not change the state of the obj.

Exercise 14.48

Determine whether the class you used in exercise 7.40 from 7.5.1(p. 291) should have a conversion to bool. If so, explain why, and explain whether the operator should be explicit. If not, explain why not.

  • No need.

Exercise 14.49

Regardless of whether it is a good idea to do so, define a conversion to bool for the class from the previous exercise.

  • Omit.

14.9.2. Avoiding Ambiguous Conversions

  • When a class has one or more conversions, we must ensure that there is only one way to convert from the class type to the target type.
  • There are two ways that multiple conversion paths can occur.
    1. The first happens when two classes provide mutual conversions. For example, class A defines a converting constructor that takes an object of class B and B itself defines a conversion operator to type A.
    2. The second way is to define multiple conversions from or to types that are themselves related by conversions. One instance is the built-in arithmetic types. A given class should define at most one conversion to or from an arithmetic type.
  • It is a bad idea to define classes with mutual conversions or to define conversions to or from two arithmetic types.

Argument Matching and Mutual Conversions

  • In the following example, we define two ways to obtain an A from a B: either by using B’s conversion operator or by using the A constructor that takes a B:
// a bad idea to have mutual conversions between two class types
struct B;
struct A
{
    A() = default;
    A(const B&);        // converts a B to an A
    // other members
};
struct B
{
    operator A() const; // also converts a B to an A
    // other members
};
A f(const A&);
B b;
A a = f(b); // error ambiguous: f(B::operator A()) or f(A::A(const B&))




<div class="se-preview-section-delimiter"></div>
  • Because there are two ways to obtain an A from a B, the compiler doesn’t know which conversion to run; the call to f is in error.
  • If we want to make this call, we have to explicitly call the conversion operator or the constructor. We can’t resolve the ambiguity by using a cast since the cast itself would have the same ambiguity.
A a1 = f(b.operator A());   // ok: use B's conversion operator
A a2 = f(A(b));         // ok: use A's constructor




<div class="se-preview-section-delimiter"></div>

Ambiguities and Multiple Conversions to Built-in Types

  • Ambiguities also occur when a class defines multiple conversions to/from types that are themselves related by conversions, e.g., when a class defines constructors from or conversions to more than one arithmetic type.
struct A
{
    A(int = 0);         // bad idea to have two conversions from arithmetic types
    A(double);
    operator int() const;   // bad idea to have two conversions to arithmetic types
    operator double() const;
    // other members
};
void f2(long double);
A a;
f2(a);      // error ambiguous: f(A::operator int()) or f(A::operator double())
long lg;
A a2(lg);   // error ambiguous: A::A(int) or A::A(double)




<div class="se-preview-section-delimiter"></div>
  • When two user-defined conversions are used, the rank of the standard conversion, if any, preceding or following the conversion function is used to select the best match. The call to f2 and the initialization of a2 are ambiguous because the standard conversions that were needed had the same rank(6.6.1).
short s = 42;
// promoting short to int is better than converting short to double
A a3(s); // uses A::A(int)




<div class="se-preview-section-delimiter"></div>
  • Since promoting a short to an int is preferred to converting the short to a double, a3 is constructed using the A::A(int) constructor, which is run on the(promoted) value of s.

Caution: Conversions and Operators

  • Don’t define mutually converting classes: if class Foo has a constructor that takes an object of class Bar, do not give Bar a conversion operator to type Foo.
  • Avoid conversions to the built-in arithmetic types. If you define a conversion to an arithmetic type, then
    1. Do not define overloaded versions of the operators that take arithmetic types. If users need to use these operators, the conversion operation will convert objects of your type, and then the built-in operators can be used.
    2. Do not define a conversion to more than one arithmetic type. Let the standard conversions provide conversions to the other arithmetic types.
  • Summary: With the exception of an explicit conversion to bool, avoid defining conversion functions and limit nonexplicit constructors to those that are obviously right.

Overloaded Functions and Converting Constructors

  • Choosing among multiple conversions is more complicated when we call an overloaded function. If two or more conversions provide a viable match, then the conversions are considered equally good.
  • Ambiguity problems can arise when overloaded functions take parameters that differ by class types that define the same converting constructors.
struct C
{
    C(int);
    // other members
};
struct D
{
    D(int);
    // other members
};
void manipulate(const C&);
void manipulate(const D&);
manipulate(10); // error ambiguous: manipulate(C(10)) or manipulate(D(10))




<div class="se-preview-section-delimiter"></div>
  • The caller can disambiguate by explicitly constructing the correct type:
manipulate(C(10)); // ok: calls manipulate(const C&)




<div class="se-preview-section-delimiter"></div>
  • Needing to use a constructor or a cast to convert an argument in a call to an overloaded function frequently is a sign of bad design.

Overloaded Functions and User-Defined Conversion

  • In a call to an overloaded function, if two(or more) user-defined conversions provide a viable match, the conversions are equally good. The rank of any standard conversions that might or might not be required is not considered. Whether a built-in conversion is also needed is considered only if the overload set can be matched using the same conversion function.
  • For example, our call to manipulate would be ambiguous even if one of the classes defined a constructor that required a standard conversion for the argument:
struct E
{
    E(double);
    // other members
};
void manip2(const C&);
void manip2(const E&);
// error ambiguous: two different user-defined conversions could be used
manip2(10); // manip2(C(10) or manip2(E(double(10)))




<div class="se-preview-section-delimiter"></div>
  • In this case, C has a conversion from int and E has a conversion from double. For the call manip2(10), both manip2 functions are viable:
    1. manip2(const C&) is viable because C has a converting constructor that takes an int. That constructor is an exact match for the argument.
    2. manip2(const E&) is viable because E has a converting constructor that takes a double and we can use a standard conversion to convert the int argument in order to use that converting constructor.
  • Because calls to the overloaded functions require different user-defined conversions from one another, this call is ambiguous. Even though one of the calls requires a standard conversion and the other is an exact match, the compiler will still flag this call as an error.
  • Summary: In a call to an overloaded function, the rank of an additional standard conversion(if any) matters only if the viable functions require the same user-defined conversion. If different user-defined conversions are needed, then the call is ambiguous.

Exercises Section 14.9.2

Exercise 14.50

Show the possible class-type conversion sequences for the initializations of ex1 and ex2. Explain whether the initializations are legal or not.

struct Long_Double
{
    Long_Double(double = 0.0);
    operator double();
    operator float();
};
Long_Double Obj;
int ex1 = Obj;
float ex2 = Obj;




<div class="se-preview-section-delimiter"></div>




<div class="se-preview-section-delimiter"></div>

#include <iostream>

using namespace std;

struct Long_Double
{
    Long_Double(double = 0.0)
    {
        cout << "Constructor\n";
    }
    operator double()
    {
        cout << "double\n";
        return 1.2;
    }
    operator float()
    {
        cout << "float\n";
        return 1.2;
    }
};

int main()
{
    Long_Double Obj;
    int ex1 = Obj;  // error: conversion from ‘Long_Double’ to ‘int’ is ambiguous
    float ex2 = Obj;    // ok: use `operator float()`

    return 0;
}




<div class="se-preview-section-delimiter"></div>

Exercise 14.51

Show the conversion sequences(if any) needed to call each version of calc and explain why the best viable function is selected.

void calc(int);
void calc(Long_Double);
double dval;
calc(dval); // which calc?




<div class="se-preview-section-delimiter"></div>
  • void calc(int);: cause class-type conversion is the lowest ranked.
    review the order:
    1. exact match
    2. const conversion
    3. promotion
    4. arithmetic or pointer conversion
    5. class-type conversion

14.9.3. Function Matching and Overloaded Operators

  • Overloaded operators are overloaded functions. Normal function matching(6.4) is used to determine which operator built-in or overloaded to apply to a given expression. When an operator function is used in an expression, the set of candidate functions is broader than when we call a function using the call operator. If a is a class type, the expression a op b might be
a.operator_op(b);       // a has operator op as a member function
operator_op(a, b);  // operator_op is an ordinary function




<div class="se-preview-section-delimiter"></div>
  • Unlike ordinary function calls, we cannot use the form of the call to distinguish whether were calling a nonmember or a member function.
  • When we use an overloaded operator with an operand of class type, the candidate functions include ordinary nonmember versions of that operator and the built-in versions of the operator. If the left-hand operand has class type, the overloaded versions of the operator, if any, defined by that class are also included.
  • When we call a named function, member and nonmember functions with the same name do not overload one another. There is no overloading because the syntax we use to call a named function distinguishes between member and nonmember functions.
  • When a call is through an object of a class type(or through a reference or pointer to such an object), then only the member functions of that class are considered.
  • When we use an overloaded operator in an expression, there is nothing to indicate whether were using a member or nonmember function. Therefore, both member and nonmember versions must be considered.
  • The set of candidate functions for an operator used in an expression can contain both nonmember and member functions.
class SmallInt
{
    friend SmallInt operator+(const SmallInt&, const SmallInt&);
public:
    SmallInt(int = 0); // conversion from int
    operator int() const
    {
        return val; // conversion to int
    }

private:
    std::size_t val;
};




<div class="se-preview-section-delimiter"></div>
  • We can use this class to add two SmallInt, but we will run into ambiguity problems if we attempt to perform mixed-mode arithmetic:
SmallInt s1, s2;
SmallInt s3 = s1 + s2;  // uses overloaded operator+
int i = s3 + 0;         // error: ambiguous




<div class="se-preview-section-delimiter"></div>
  • The first addition uses the overloaded version of + that takes two SmallInt values. The second addition is ambiguous because we can convert 0 to a SmallInt and use the SmallInt version of +, or convert s3 to int and use the built-in addition operator on ints.

Exercises Section 14.9.3

Exercise 14.52

Which operator+, if any, is selected for each of the addition expressions? List the candidate functions, the viable functions, and the type conversions on the arguments for each viable function:

struct LongDouble
{
    // member operator+ for illustration purposes; + is usually a nonmember
    LongDouble operator+(const SmallInt&); // 1
    // other members as in § 14.9.2 (p. 587)
};
LongDouble operator+(LongDouble&, double); // 2
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;




<div class="se-preview-section-delimiter"></div>
  • ld = si + ld; is ambiguous.
  • ld = ld + si can use both 1 and 2, but 1 is more exactly. For 2, SmallInt need to convert to double.

Exercise 14.53

Given the definition of SmallInt on page 588, determine whether the following addition expression is legal. If so, what addition operator is used? If not, how might you change the code to make it legal?

SmallInt s1;
double d = s1 + 3.14;




<div class="se-preview-section-delimiter"></div>
  • ambiguous.
SmallInt s1; 
double d = s1 + SmallInt(3.14);

Chapter Summary

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

你可能感兴趣的:(github,basic)