13

Many standard library algorithms take predicate functions. However, the type of these predicates is an arbitrary, user-provided template parameter. Why doesn't C++11 specify that these take a specific type, like std::function instead? For example:

template< class InputIt >
InputIt find_if( InputIt first, InputIt last,
             std::function<bool()> p );

Isn't using this instead of a template as argument type not much more clean?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
paul23
  • 8,799
  • 12
  • 66
  • 149
  • Templates are less expensive. – Pubby Mar 14 '13 at 12:25
  • Please be more detailed about what you mean by "templates". `std::function` is a class template too, y'know. – Lightness Races in Orbit Mar 14 '13 at 12:29
  • Even if std::function would be a better idea, you probably can't just change the entire standard library to use it without breaking existing code. And because all the standard algorithms are templated on the iterator type anyway, you don't actually gain anything by making the predicate not be a template parameter. – Sebastian Redl Mar 14 '13 at 12:43
  • 3
    Five upvotes for nonsense. The third _function argument_ to the function `find_if` is never a template. C++ does not allow you to "pass templates" (except as template arguments themselves). The third argument is an _object_ of a real type, whether that type is an instantiation of a template or not. With your own home-brew predicates that is _sometimes_ the case; with `std::function` that is _always_ the case. **"`std::function` instead of templates" simply makes no sense.** – Lightness Races in Orbit Mar 14 '13 at 13:03
  • 11
    @LightnessRacesinOrbit: It's funny; the rest of us were able to figure out what he was asking just fine, despite it being poorly worded in terms of standard lingo. It's clear he's talking about the predicate's type being a template typename rather than a specific type such a `std::function<...>`. Stop being so pedantic; it's obvious what he's talking about if you stop trying to parse everything exactly according to standardese. – Nicol Bolas Mar 14 '13 at 13:08
  • @Nicol: In this instance, I'm genuinely unsure what the hell is going on. This question is very confused. I don't know what question you guys are reading but I don't think it's this one! A predicate's type can never be "a template typename". What does that even mean? A function argument is never a template. Ever. Ever. – Lightness Races in Orbit Mar 14 '13 at 13:10
  • @LightnessRacesinOrbit `std::find_if` etc. take a predicate typed as one of their template parameters. The question is why they don't take their predicate typed as `std::function<:::>` instead. – Angew is no longer proud of SO Mar 14 '13 at 13:22
  • @LightnessRacesinOrbit: "*A predicate's type can never be "a template typename".*" Are you *trying* to misunderstand? Let me spell it out for you: `**template**<..., **typename** PredicateType> find_if(..., **PredicateType** arg);` The "predicate's type" is a "template" "typename". Do you get it yet? – Nicol Bolas Mar 14 '13 at 13:27
  • @Nicol Wtf are you going on about? The function that you call, which is an instantiation of `find_if`, takes an object of type `PredicateType`, whatever type that ends up being. The function does not receive a template. It receives an object. That predicate object has a type, and that type is not a template. It is a type. This is the case whether you use your own home-brew predicate or one wrapped in `std::function`, and it makes no sense to say that only one of these options is "using templates". So the question "should I use templates, or `std::function`?" is entirely non-sensical. – Lightness Races in Orbit Mar 14 '13 at 13:31
  • @Xeo has helped me to understand (a) Nicol's answer, and (b) what the OP _meant_ to ask. I maintain that the question is horribly confused but, at least, I no longer am. – Lightness Races in Orbit Mar 14 '13 at 16:06
  • 1
    possible duplicate of [std::function vs template](http://stackoverflow.com/questions/14677997/stdfunction-vs-template) – ildjarn Mar 14 '13 at 18:06

3 Answers3

10

std::function is for runtime polymorphism. Any particular std::function instance could be storing a functor of any type (a type that's appropriate for the std::function's signature, of course).

Standard library algorithms and such are templated on the type of their function parameters. As such, they don't need runtime polymorphism to do their job; they rely on compile-time polymorphism.

Most important of all, such algorithms don't need to force the cost of runtime polymorphism on you. If you want runtime polymorphism, you can send it a std::function or whatever. If you want compile-time polymorphism, you provide it a type that doesn't use polymorphic dispatch (aka: most functors or functions).

The cost of runtime polymorphism also includes the inability to inline the function call. Using a proper functor (or even function pointers, depending on how good your compiler is), the compiler can generally inline the function call if it so desires. With runtime polymorphism, not only are you paying the cost of the runtime dispatch (which may include additional parameter forwarding costs), you're also loosing important optimization opportunities.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • `find_if` doesn't "take" templates. It takes objects. There is nothing compile-time about "using templates" any more than there is about "using `std::function`" in this instance. – Lightness Races in Orbit Mar 14 '13 at 12:31
  • @LightnessRacesinOrbit: I never said anything about taking templates. I said they *are* templates. – Nicol Bolas Mar 14 '13 at 12:31
  • Great, except, so what? So is `std::function`. – Lightness Races in Orbit Mar 14 '13 at 12:32
  • 7
    @LightnessRacesinOrbit: I thought it was obvious. The type of function object taken by the template is part of the template's type. Therefore, each instantiation of the template can, *at compile-time* know exactly what it's taking. The polymorphism (the ability to call different functions) is done statically at compile-time. `std::function` uses runtime-polymorphism. Any particular `std::function` object could be storing a function object of any type appropriate to its signature. – Nicol Bolas Mar 14 '13 at 12:35
  • 4
    I agree with this answer. I think it's also worth mentioning that when passing a lambda as an argument of deduced template parameter type, compilers often inline the call to the lambda. They don't do this with `std::function` - this is of course something that might improve as the compiler support for C++11 matures. – Joseph Mansfield Mar 14 '13 at 12:39
  • @Nicol Sorry I have absolutely no idea what you're talking about. I don't see what runtime polymorphism has to do with anything. But, since four people evidently disagree, I'll just back away slowly now... – Lightness Races in Orbit Mar 14 '13 at 13:00
  • @sftrabbit: since you can assign to a function any callable taking the same arguments, you cannot get rid of some sort of dynamic dispatch, and even if compilers become better, std::function will always incur a small overhead because of its flexibility. – Antoine Mar 14 '13 at 13:17
8

Performance!

Template base function is very very better than std::function mode. I did this test for you:

template <typename F>
void test1(const F &f)
{
    for (unsigned long long i = 0; i < 19000000; i++)
        f();
}

void test2(function<void()> &f)
{
    for (unsigned long long i = 0; i < 19000000; i++)
        f();
}

int main()
{
    {
    LARGE_INTEGER frequency, start, end;
    double interval;
    QueryPerformanceFrequency(&frequency);
    QueryPerformanceCounter(&start);

    unsigned long long x = 0;
    test1([&x]()
    {
        x++;
    });

    QueryPerformanceCounter(&end);
    interval = (double) (end.QuadPart - start.QuadPart) / frequency.QuadPart;

    cout << "Template mode: " << interval << " " << x << endl;
    }
    {
    LARGE_INTEGER frequency, start, end;
    double interval;
    QueryPerformanceFrequency(&frequency);
    QueryPerformanceCounter(&start);

    unsigned long long x = 0;
    function<void() > f = [&x]()
    {
        x++;
    };
    test2(f);

    QueryPerformanceCounter(&end);
    interval = (double) (end.QuadPart - start.QuadPart) / frequency.QuadPart;

    cout << "std::function mode:" << interval << " " << x << endl;
    }
}

Template mode: 2.13861e-006

std::function mode:0.220006

gcc 4.7.2 on Windows7 -O2 Core2 Duo CPU 2.40GHz

masoud
  • 55,379
  • 16
  • 141
  • 208
  • 1
    And why a factor of 2000? Because the lambda version can be reduced to `cout << 19000000 << endl` by the compiler. Meanwhile, the compiler isn't smart enough to figure out what is inside the `std::function` (this is a failure of the compiler, but a common one), so has to actually do the calls. Strip the `cout` out of the performance check for them (why time `cout`?), and you'll get an even larger ratio... – Yakk - Adam Nevraumont Mar 14 '13 at 14:47
  • It's over 10 million. (and that is probably just "create high performance timer, finish high performance timer" overhead) – Yakk - Adam Nevraumont Mar 14 '13 at 15:05
3

Because std::function is imperfect.

How is it imperfect? Let me enumerate the ways.

{

First, std::function doesn't support perfect fowarding of arbitrary objects passed in to it. And, in practice, it cannot. std::function exposes one fixed signature to callers, and can accept many kinds of callees, and perfect forwarding requires a custom-crafted signature for each caller and callee. It does support perfect forwarding of exactly the arguments it exposes in its signature, but that isn't sufficient.

Imagine a std::function that takes two arguments, and int and a double. For it to do perfect forwarding, it would have to accept int&, int&& and int const&, times the same set for double (let alone volatile varients thereof). The number of signatures that each std::function has to accept to pull off perfect forwarding grows exponentially with the number of arguments it has. std::function's set of signatures it exposes (currently 1) is fixed at instantiation, while a templates set of signatures it exposes is unlimited and only generated when used. This is important because some function-like objects do optimize for these cases differently! So every std::function you have removed opportunities to perfectly forward calls to the wrapped type.

A second reason why std::function is imperfect is because compilers suck. If you wrap a lambda in a std::function then call an algorithm with it, the compiler in theory could realize that this std::function is wrapping some fixed lambda -- but in practice, it loses track of this fact, and treats the std::function as a wrapper around some generic class. So even in cases where the signature of the std::function exactly matches the use-cases of the algorithm, preventing the type-bottleneck of std::function from making the forwarding imperfect, in practice there will be overhead due to the type-erasure performed by std::function, and the compiler will find it hard to optimize over the std::function call "barrier".

A third reason why std::function is imperfect is that it would encourage algorithm writers to overly restrict what parameters can be passed in algorithms. If you examine find_if, the naive assumption is that the thing you are looking for should be the same type as the types stored in the container, or at least convertible: but the std::find_if algorithm only requires that they be comparible under the passed in functor.

This allows you to write multi-type aware functors and pass in a target object that is of an unrelated type to the type on the container, and things work just fine. Most people don't need this, and their code works find without it -- which is good as well.

The naive std::find_if would extract the underlying type of the container, and the comparison function would be between pairs of that type -- or, it would be a 4 way comparison between container types and the type of the thing being searched for. In one case, we lose flexibility -- in the other case, everyone pays for a strange corner case. And in C++, you should only pay for the features you need when you need them!

A forth reason why std::function is imperfect is that it is fundamentally a tool of type erasure. These are implementation details, but I'm not aware of a compiler which strays far from them. A std::function's purpose is expose a single signature and return value, and say "I can store anything that matches this signature and this return value, and you can call it". It exposes a static run-time interface and implementation to do this task. When you initialize a std::function, it does compile-time work to generate a helper object that wraps that particular object in the uniform std::function interface, then stores that in the pImpl pattern. All of this is work that is unnecessary if you don't need type erasure.

The standard algorithms are about writing high level code that is nearly as efficient as hand-crafted solutions. Even the cost of a pointer function call isn't required to solve most of these problems, let along a virtual call through a type erased std::function.

}; // enum

std::function is an awesome tool for callbacks, to replace boilerplate single-purpose virtual interfaces, and when you need to hide the implementation details from the caller (say, you need to cross compilation unit boundaries for design decisions).

The good news is that better solutions to this problem are coming down the pipe. In particular, one of the goals for C++14 or C++17 is that it is going to have some kind of "concept" support, where you can say "this template argument has the following properties". What the exact syntax will be is far from certain -- the C++11 Concept proposal is probably going way to far -- but there is lots of enthusiasm about it, and there is a working group on the problem right now.

When or if it is done, you'll be able to mark up the functor with meaningful concept information that says "this argument isn't just any type, but a type that is a functor that takes two values (including the contained data types), and returns a bool compatible value" that the compiler, your IDE, and you can understand without having to go to the documentation for the function.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524