Skip to content

Latest commit

 

History

History
318 lines (257 loc) · 10.1 KB

Functions.org

File metadata and controls

318 lines (257 loc) · 10.1 KB

Function Objects

Function Objects are created by overloading the function call operator operator(). Function objects may truly be defined inline. Functions that are called indirectly (i.e., using pointers to functions) can never be defined inline as their addresses must be known. An added benefit of function objects is that they may access the private data of their objects.

Constructing a manipulator

The class ostream has an overloaded operator<< accepting a pointer to a function expecting an ostream & and returning an ostream &; and another operator<< is defined

inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1
basic_ostream& operator<<(basic_ostream& (*__pf)(basic_ostream&))
{ return __pf(*this); }

inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1
basic_ostream& operator<<(basic_ios<char_type, traits_type>&
                          (*__pf)(basic_ios<char_type,traits_type>&))
{ __pf(*this); return *this; }

inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1
basic_ostream& operator<<(ios_base& (*__pf)(ios_base&))
{ __pf(*this); return *this; }    

These operator overloadings does not accept manipulators with arguments like setprecision(n).

class __iom_t5
{
    int __n_;
public:
    _LIBCPP_INLINE_VISIBILITY
    explicit __iom_t5(int __n) : __n_(__n) {}

    template <class _CharT, class _Traits>
    friend
    _LIBCPP_INLINE_VISIBILITY
    basic_istream<_CharT, _Traits>&
    operator>>(basic_istream<_CharT, _Traits>& __is, const __iom_t5& __x)
    {
        __is.precision(__x.__n_);
        return __is;
    }

    template <class _CharT, class _Traits>
    friend
    _LIBCPP_INLINE_VISIBILITY
    basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>& __os, const __iom_t5& __x)
    {
        __os.precision(__x.__n_);
        return __os;
    }
};

inline _LIBCPP_INLINE_VISIBILITY
__iom_t5
setprecision(int __n)
{
    return __iom_t5(__n);
}

Another way is to implement a manipulator as a class overloading operator() and operator<<.

class Align
{
    unsigned d_width;
    std::ios::fmtflags d_alignment;

    public:
        Align(unsigned width, std::ios::fmtflags alignment);
        std::ostream &operator()(std::ostream &ostr) const;
};

Align::Align(unsigned width, std::ios::fmtflags alignment)
:
    d_width(width),
    d_alignment(alignment)
{}

std::ostream &Align::operator()(std::ostream &ostr) const
{
    ostr.setf(d_alignment, std::ios::adjustfield);
    return ostr << std::setw(d_width);
}

std::ostream &operator<<(std::ostream &ostr, Align &&align)
{
    return align(ostr);
}

Lambda Expressions

A lambda expression defines an anonymous function object which may immediately be passed to functions expecting function object arguments. Lambda expressions provide a concise way to create simple function objects. The emphasis here is on simple: a lambda expression’s size should be comparable to the size of inline-functions. If you need more code, then encapsulate that code in a separate function which is then called from inside the lambda expression’s compound statement, or consider designing a separate function object.

When a called function must remember its state a function object is appropriate, otherwise a plain function can be used.

A lambda expression defines an anonymous function object, also called a closure object or simply a closure. When a lambda expression is evaluated it results in a temporary function object (the closure object). This temporary function object is of a unique anonymous class type, called its closure type.

[]  // labmda-introducer
(int x, int y) // lambda-declarator
{
    return x + y; //normal compound statement
}

A lambda can be remove its const=ness by specifying =mutable, that is, operator()(args...) without const so that by-value-captured variables can be modified.

[] (int x, int y) mutable
...

The lambda-declarator may be omitted if no parameters are defined, but when specifying mutable (or constexpr, see below) a lambda-declarator must be specified (at least as an empty set of parentheses). The parameters in a lambda declarator cannot be given default arguments.

Declarator specifiers can be mutable, constexpr, or both. A constexpr lambda-expression is itself a constexpr, which may be compile-time evaluated if its arguments qualify as const-expressions. By implication, if a lambda-expression is defined inside a constexpr function then the lambda-expression itself is a constexpr, and the constexpr declarator specifier is not required.

If there are multiple return statements returning values of different types then the lambda expression’s return type must explicitly be specified using a late-specified return type

Although lambda expressions are anonymous function objects, they can be assigned to variables. Often, the variable is defined using the keyword auto.

auto sqr = [](int x) {
    return x * x
};

Capturing variables

Variables visible at the location of a lambda expression may be accessible from inside the lambda expression’s compound statement.

When the lambda expression is defined inside a class member function the lambda-introducer may contain this or ∗this.

Global variables are always accessible, and can be modified if their definitions allow so. Local variables of the lambda expression’s surrounding function may also be specified inside the lambda-introducer. Any capture may appear only once.

[] (mutable)        // access to merely global variables
[this] (mutable)    // all the object's data members which can be modified
[*this]            // access to all the object's members which cannot be modified
[*this] mutable     // modifiable copies are used inside the lambda expression without affecting the object's own data

[local]
[this, local]
[*this, local]
// `local` is immutably accessed

[local] mutable
[this, local] mutable
[*this, local] mutable
// `local` is available as the a local copy

[&local] (mutable)
[this, &local] (mutable)
[*this, &local] (mutable)
// local is available by modifiable reference of the surrounding function's local variable
  • & (implicitly capture the used automatic variables by reference)
  • === (implicitly capture the used automatic variables by copy).
[=]
[=, this]           // (C++17)
[=, *this]          // (C++20)
// local variables are visible but not modifiable

[=] mutable
[=, this] mutable   // (C++17)
[=, *this] mutable // (C++20)
// local variables are visible as modifiable copies.

[=, &local] (mutable)
// `local` is accessed by modifiable reference

[&] (mutable)
[&, this] (mutable)
// local variables are visible as modifiable references

[&, *this] (mutable) // 

[&, local] (mutable)
[&, this, local] (mutable)
[&, *this, local] (mutable)
// `local` is accessed as a modifiable copy

The current object (*this) can be implicitly captured if either capture default is present. If implicitly captured, it is always captured by reference, even if the capture default is ===. Even when not specified, lambda expressions implicitly capture their this pointers, and class members are always accessed relative to this.

When a lambda captures a member using implicit by-copy capture, it does not make a copy of that member variable: the use of a member variable m is treated as an expression (*this).m, and *this is always implicitly captured by reference.

Use

Named lambda expressions nicely fit in the niche of local functions: when a function needs to perform computations which are at a conceptually lower level than the function’s task itself, then it’s attractive to encapsulate these computations in a separate support function and call the support function where needed.

use lambda expressions sparingly. When they are used make sure that their sizes remain small. As a rule of thumb: lambda expressions should be treated like in-line functions, and should merely consist of one, or maybe occasionally two expressions.

One may even call a lambda immediately where it is defined, as a scoped expression that returns a value, for example, to initialize some objects that is context-dependent.

Overloading operator|

Combining enum values using arithmetic results in int-types values. it is possible to overload the enum type’s operators.

enum Permission {
    READ =   1 << 0,
    WRITE    1 << 1,
    EXECUTE  1 << 2
};

void setPermission(Permission perm);

Permission operator|(Permission left, Permission right) 
{
    return static_cast<Permission>(static_cast<int>(left) | right);
}

Using defined literals (extensible literals)

A user-defined literal is defined by a function that must be defined at namespace scope. Such a function is called a literal operator. A literal operator cannot be a class member function.

Type operator""_identifier(parameter-list);

Note that when the paramter is const char *, the literal must not be given double quotes and must represent a numeric constant.

double operator""_NM2km(const char *nm)
{
    return std::stod(nm) *1.852;
}

Arguments to literal operators are themselves always constants.