Posted: Mar 11, 2012 2:28 pm
by VazScep
mizvekov wrote:So yes, it is parametrized to work with any of the float types.
The thing is that instances of classes can be used as functions, if you overload the function call operator '()' for them. These
kinds of classes are usually called Functors in the literature. Here is an example:
Code: Select all
struct B {
    int operator()(int a, int b) { return a + b; }
};

With this you can do:
Code: Select all
B b;
int c = b(2, 3); // c = 5

And indeed, these functors are what many people used to implement lambdas in the past before the standard supported it.
Yes. They effectively appear in Java as well, when you have a class with a single method such as "doIt".

There is a limited correspondence between lambdas and classes. Just as lambdas can be classes with syntax sugar, a lot of classes can be records of lambdas. For instance, in Standard ML, I could fake the implementation of a Counter class which keeps track of a value that can be reset as follows:

Code: Select all
let new_counter reset =
  let m_x      = ref reset in
  let incr ()  = m_x := !m_x + 1 in
  let reset () = m_x := reset in
  let get ()   = !m_x in
  (incr, reset, get)

let counter_incr (incr,_,_)   = incr ()
let counter_reset (_,reset,_) = reset ()
let counter_get (_,_,get)     = get ()

The ugliness of tuples and those last three functions is usually buried under the syntax sugar of records (amusingly, a number of Lisps implement records as nothing more than a bunch of macros to write exactly this sort of code).

You can even have encapsulation based on which values you include in the returned tuple: here, I leave m_x as a "private" value, since it cannot be accessed outside the function.

The only thing I think that is significantly missing from this picture is subtyping. And for this reason, Ocaml has its own object system, and supports subtyping in a manner which is definitely worth a look. The idea is that the subtyping is structural rather than nominal. In virtually all languages with subtyping, including C++, the type hierarchy is a directed graph which is explicitly coded by the programmer by associating each class with its named supertypes. In Ocaml, one class is a subtype of another just in case it supports all of the other's methods (this is sometimes called "duck-typing" by Python programmers, despite Python not having a type-system). Moreover, structural types can be anonymous using so-called "row-polymorphism." Suppose I define a function foo which takes an x and invokes its bar and baz methods as follows (# accesses instance methods):

Code: Select all
let foo x = x#bar + List.length x#baz


Then Ocaml will infer the type of foo as:

Code: Select all
val foo : < bar : int; baz : 'a list; .. > -> int = <fun>

This says that for any type 'a, foo is a function which takes some object supporting both a "bar : int" method and a "baz : 'a list" method, and returns an int. It can now be used with any instance of a class which happens to support these methods, even if there is no explicit subtyping involved.

For example, here is how you would have written the example I gave using Functors instead:
Code: Select all
struct B {
        int val;
        B(int v) : val(v) {}
        ~B() { std::cout << "bye " << val << std::endl; }
};

std::function<int(int)> f(std::shared_ptr<B> b) {
        //here is different, we use Functors instead of
        //return [b](int a) -> int { return a + b->val; };
        //notice how it is much more verbose
        struct Functor {
                std::shared_ptr<B> b;
                Functor(std::shared_ptr<B> b_) : b(b_) {}
                int operator()(int a) { return a + b->val; }
        };

        Functor f(b);
        return f;
}

std::function<int(int)> g() {
        auto a = std::make_shared<B>(10), b = std::make_shared<B>(20);
        return f(a);
}
int main() {
        {
                auto foo = g();
                auto bar = std::bind(foo, 1);
                std::cout << foo(2) << ' ' << bar() << std::endl;
        }
        std::cout << "the end" << std::endl;
}

It's much more cumbersome, but you can think of it as essentially the same thing as lambdas. Indeed, this is probably how the compilers implement them internally. The differences though are that there is no "this" pointer of the lambda itself which you can access, and that the variables which are captured are declared as "const" by default.
The first point allows you to use the parents 'this' pointer seamlessly.
Example:
Code: Select all
struct Foo {
     int var;
     std::function<void()> bar() {
            return [this]() {
                 var = 10; // accessing the parents 'var' field implicitly through the this pointer
                               // it's something more cumbersome to do with Functors...
            };
     }
};

The second point can be changed by declaring the lambda mutable, like this:
Code: Select all
[b]() -> int mutable { return b++; }

With the above, only the lambda's internal copy of b changes.
So you can have lambdas with mutable state, which can sometimes be necessary.

By the way, as an aside, I have been declaring the lambdas to capture explicitly, just to be more clear what is happening,
but you can capture implicitly.
Examples:
Code: Select all
[=]( ... ) { ... } //capture all variables by value
[&]( ... ) { ... } //capture all variables by reference
[&, b]( ... ) { ... } //capture all variables by reference, except b, which is to be captured by value

And variables are only captured if either they are captured explicitly, or else there is a default capture AND they are actually used in the code.
So it is pretty safe to just capture by default.
Okay. I hadn't actually noticed this, but yes, it'd have hoped there would be a mechanism which did the variable capture automatically.

On this matter, this is where I still feel C++ has got things right compared with nearly every other OO language: having a copy-semantics, a reference-semantics and a const keyword, so that reasoning about mutability isn't such a nightmare.

Here, I'm guessing that if you want a lambda to capture a variable that will be mutated from outside, and you want the lambda to see the update, you would generally use capture by reference? Other times, you might want the lambda to have its own copy, and you would use mutable (for primitive types?) and use capture by value otherwise?

VazScep wrote:std::bind is actually declared as a template, but you don't have to specify the type manually because it can infer from the type of the target. That means that you can use it with any type which you can use the function call operator, and that means any conventional function, class methods, Functors (which are the classes that overload the function call operator, as I said above), lambdas, and most probably anything a future standard defines which satisfies the requirement.
Okay, that's very cool.

VazScep wrote:By the way, as another aside, I think I may have made that example more confusing by writing it as a one liner.
Yes, my C++ knowledge is very rusty. I'd even forgotten entirely about initialisation lists, but suddenly remembered them when I saw the code!