Std::Function VS Template

std::function vs template

In general, if you are facing a design situation that gives you a choice, use templates. I stressed the word design because I think what you need to focus on is the distinction between the use cases of std::function and templates, which are pretty different.

In general, the choice of templates is just an instance of a wider principle: try to specify as many constraints as possible at compile-time. The rationale is simple: if you can catch an error, or a type mismatch, even before your program is generated, you won't ship a buggy program to your customer.

Moreover, as you correctly pointed out, calls to template functions are resolved statically (i.e. at compile time), so the compiler has all the necessary information to optimize and possibly inline the code (which would not be possible if the call were performed through a vtable).

Yes, it is true that template support is not perfect, and C++11 is still lacking a support for concepts; however, I don't see how std::function would save you in that respect. std::function is not an alternative to templates, but rather a tool for design situations where templates cannot be used.

One such use case arises when you need to resolve a call at run-time by invoking a callable object that adheres to a specific signature, but whose concrete type is unknown at compile-time. This is typically the case when you have a collection of callbacks of potentially different types, but which you need to invoke uniformly; the type and number of the registered callbacks is determined at run-time based on the state of your program and the application logic. Some of those callbacks could be functors, some could be plain functions, some could be the result of binding other functions to certain arguments.

std::function and std::bind also offer a natural idiom for enabling functional programming in C++, where functions are treated as objects and get naturally curried and combined to generate other functions. Although this kind of combination can be achieved with templates as well, a similar design situation normally comes together with use cases that require to determine the type of the combined callable objects at run-time.

Finally, there are other situations where std::function is unavoidable, e.g. if you want to write recursive lambdas; however, these restrictions are more dictated by technological limitations than by conceptual distinctions I believe.

To sum up, focus on design and try to understand what are the conceptual use cases for these two constructs. If you put them into comparison the way you did, you are forcing them into an arena they likely don't belong to.

std::function vs callable as template parameter

Even though you specified <uint32_t> as a template argument, the compiler seems to try to deduce more elements for the parameter pack, fails to do so (because the type of a lambda is not std::function<...>), and becomes upset.

You need to somehow inhibit template argument deduction.

Either call it as exec0<uint32_t>({_g}, {_h});, or wrap parameter types in std::type_identity_t<...> (or, pre-C++20, std::enable_if_t<true, ...>).

Then the compiler will accept your uint32_t as the only type in the pack, and won't try to add more types.

how to use std::function to point to a function template

No. A template function is exactly that, a template. It's not a real function. You can point a std::function to a specific instantiation of the template function, e.g. func<int,int>

std::function performance as compared to templates

When I compile your program (with -O3 optimization) and use calc1, the execution time is 0.0 seconds. This is because the compiler can completely optimize away the code. It knows your code doesn't actually do anything, so there's no sense in running any of it.

When I compile your program (again with -O3 optimization) and use calc2 (which uses std::function), the program takes 2 seconds to run. The reason it takes longer is because the optimizer can't optimize everything away. std::function works at runtime (not compile time, because it has to do type erasure; see this question and this question), and in general the optimizer can't inline (or entirely optimize away) calls that go through std::function (in this situation, it's technically possible for the optimizer to do so, since this is a simple program, but it doesn't).


The reason std::function calls can't be inlined is because the compiler doesn't always know what std::function will do. In this code, it's simple enough that the compiler's static analyzer could, if it was "smart" enough, actually inline the whole thing and then optimize it away.

But that can be a tricky thing to implement in a compiler, and it doesn't make a very big difference in "real" programs that are more complex. In more complex programs, it can actually be impossible to know what std::function will do. For example, imagine you have a second .cpp file that calls calc2 with a different std::function. Or imagine if you set your std::function to one of two different lambdas, depending on user input. The compiler wouldn't know which lambda to actually call until the program ran, so it couldn't just optimize everything away. Because of issues like this, it's not really worth the effort of implementing deep static analysis for std::function that would completely optimize away your simple code.

Template functors vs functions

There are two main reasons: The first is, as pythonic metaphor noted, partial specialization is only valid for classes and not functions. Note that functions can use overloads to overcome this problem generally, but often if you are doing metaprogramming it's easier and more generic to use partial specialization. I'd actually think this was the main reason.

The second reason is that anytime that code wants to accept a function object (like in the STL, e.g. std::transform), it will have a type template parameter. If you pass a functor or a lambda, the exact type is known at compile time, and you don't pay for indirection, and inlining can be performed. If you pass a function pointer (or a std::function), only the signature is known at compile time, and you pay for an indirection (and you can't inline). For instance, std::sort can be considerably faster with a functor than a function pointer.

Note that there is a little used feature called function pointer template parameters; these are non type template parameters that specialize on a specific function, and thus can remove indirection. However, if you use one of these, you can't use a functor at all. So most code that wants to accepts a function object does it the way I described above.

Template parameter in std::function

Third argument to function run is declared to be std::function<double(const T &, const T &)>, however you pass a pointer to scoreValue1 causing type deduction to be impossible. In order to deal with this problem you need to either

  1. declare argument to be a pointer to function
void run
(
const std::vector<T> & dataA
, const std::vector<T> & dataB
, double (* function )(const T & a, const T & b)
)

online compiler


  1. Put T of third argument into non-deducible context:
void run
(
const std::vector<T> & dataA
, const std::vector<T> & dataB
, std::function<double(const ::std::type_identity_t<T> &, const ::std::type_identity_t<T> &)> function
)

online compiler

static member std::function of template class gets empty despite initalization

The problem is related to the order of initialization of static variables which I guess is solved differently for the templated instantiated static variables compared to Test<double> on different compilers.

inline static Holder f;

is a static variable, so somewhere before entering main it will be default initialized (to an empty function). But Test<double> is another static variable that will get its own initialization before entering main.

On GCC it happens that

  • Test<double> is called
  • Test<double>::f is set by the constructor of Test<double>
  • the default constructor of Test<double>::f is called, thus emptying the function

This all happens inside __static_initialization_and_destruction_0 GCC method, if you actually use a wrapper object to break on static initialization of the variable you can see what's happening: https://onlinegdb.com/UYEJ0hbgg

How could the compiler know that you plan to set a static variable from another static variable before its construction? That's why, as you said, using static variables is a bad practice indeed.

Pass template function to std::bind?

handle is not a template function. There are no "template functions". handle is a function template, ie it is a template, it is not a function. You cannot std::bind to a template. You can only std::bind to a callable.

The trick is to defer instantiation of the template and deduction of the template parameters to when the function is actually called:

#include <iostream>
#include <functional>
#include <memory>

using namespace std;

struct foo {

struct handle_caller {
template <typename T,typename U>
void operator()(foo* f, T t,U u){
f->handle(t,u);
}
};

void f()
{
auto cb = std::bind(handle_caller{},this, placeholders::_1, placeholders::_2);
}

template <typename T, typename U>
void handle(T, U)
{
}
};

int main()
{
return 0;
}

The callable passed to bind is an object of a concrete type handle_caller. It is not a template. Only when cb is called the parameters are forwarded to handle_caller::operator() where the template arguments can be deduced.

Lambdas can do this out-of-the box, because a lambda with auto arguments is of a concrete type and only its operator() is a template:

#include <iostream>
#include <functional>
#include <memory>

using namespace std;

struct foo {
void f()
{
auto cb = std::bind([](auto f,auto t,auto u){ f->handle(t,u);},this, placeholders::_1, placeholders::_2);
}

template <typename T, typename U>
void handle(T, U)
{
}
};

int main()
{
return 0;
}

However, once you use the lambda there is no need for std::bind anymore, because you can bind the parameters via a lambda capture. std::bind is the ancient way to bind parameters, it is convoluted and has clunky syntax. I have read of cases that can be done with std::bind but not with a lambda, but I have never encountered one.

PS: Note that I removed the shared_from_this stuff from your code, because I know it can be used wrong easily, but I am not sure how to use it correctly. As cb is only local to foo::f there is no need to worry about the lifetime of this in the example code.



Related Topics



Leave a reply



Submit