NoSenseEtAl
NoSenseEtAl

Reputation: 30088

How to have std::function or lambda as (optional) template parameter?

Hi I was playing around with TMP and was thinking of generating of a class that looks something like:

template<typename T, typename LogFunc>
class
{

(where LogFunc should be defaulted to "nop" function)

Idea is to have a class that defines some functionality for instances of type T, for example checks if the number is even, and also has the option to log by calling

void memberFunc(T& t)
{
  LogFunc(t); 
}

or maybe

void memberFunc(T& t)
{
  LogFunc lf;
  lf(t);
}

Can it be done? From reading A on SO, lambdas are kind of problematic as templ params. BTW if somebody cares this is what I tried but it prints out

:(

Upvotes: 0

Views: 3450

Answers (3)

Johan Lundberg
Johan Lundberg

Reputation: 27038

The other answers are all fine, but you can also just pass in a constructor argument with a std::function<T>. That looks like this:

#include <functional>
#include <iostream>

template <typename T> void someOther(T val){
  std::cout << "used other "<<val<<std::endl;
}

template <typename T> void noop(T val){
  std::cout << "noop "<<val<<std::endl;
}

template<typename T>
struct A{
  A(std::function<void(T)> f =noop<T> ) : mf(f){}
  void memberFunc(T valx){
    mf(valx);
  }
  std::function<void(T)> mf;
};

int main(){
  A<int> aNoop; ;
  A<float> aSomeOther{someOther<float>} ;
  aNoop.memberFunc(5);
  aSomeOther.memberFunc(3.55);
}

An alternative is to use functor classes, like this:

#include <iostream>
template <typename T> struct OtherC{
  void operator()(T v){ std::cout <<"other "<<v<<std::endl; };
};
template <typename T> struct NoopC{
  void operator()(T){ std::cout << "noop"<<std::endl; };
};
template<typename T, template <typename X> class F = NoopC >
struct A{
  static void memberFunc(T valx){ F<T>()(valx); }
};
int main(){
  A<int> aNoop; 
  A<float,OtherC> aSomeOther ;
  aNoop.memberFunc(5);
  aSomeOther.memberFunc(3.55);
}

Upvotes: 1

rici
rici

Reputation: 241861

The problem is that the type of a lambda is a compiler-enforced singleton; it has only one value, which is the lambda itself; furthermore, the type has a deleted constructor. So you can't pass lambdas as part of a template instantiation, even with decltype. But there's nothing stopping you from passing them as constructor arguments.

However, here we run into another problem: constructor arguments are not used to deduce a template instantiation (which is why the standard library provides utilities like make_pair and make_tuple). So we need a templatized factory function.

With all that, the solution is pretty simple:

template<typename T, typename LogFunc>
class Foo {
  public:
    Foo(const T& t, LogFunc fn) : t_(t), lfn_(fn) {}
    //...

  private:
    T t_;
    LogFunc lfn_;
};

struct Noop {
  template<typename...A>
  void operator()(A...) { };
};

template<typename T, typename LogFunc=Noop>
Foo<T, LogFunc> make_foo(const T& t, LogFunc func=LogFunc()) {
  return Foo<T, LogFunc>(t, func);
}

Upvotes: 3

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20739

This will not answer directly, but gives a number of hints about what you did.

The LogFunc parameter is a type (not an object), hence

  • LogFunc(t) creates a temporary LogFunc giving t as parameter (you are in fact calling the LogFunc::LogFunc(T&) contructor).
  • LogFunc lf; lf(t); creates a stack-living default contructed Logfunc, named lf, and lf(t) calls its LogFunc::operator()(T&) member function.
  • LogFunc()(t) creates a temporary default-constructed LogFUnc and calls operator()(T&) on it.

About lambdas, they are in fact classes whose constructor takes the captured varaibles, and whose operator() takes the parameters you declare. But they exist only "internaly" to the compiler, and don't have a "name" you can refer to.

What you can do is deduce its type with a decltype, or with a free-function.

Typically a parametric functional class stores a frunction object, initialized at construction.

#include <iostream>

template<class Fn>
class LogFunc
{
public:
    LogFunc(Fn f) :fn(f) {}

    template<class T>
    void memberFunc(T& t)
    { fn(t); }
private:
    Fn fn;
};

template<class Fn>
LogFunc<Fn> makeLogFunc(Fn f)
{ return LogFunc<Fn>(f); }

int main()
{
    int x=5;

    auto lf = makeLogFunc([](int& a){ std::cout << a << std::endl; });
    lf.memberFunc(x);

    return 0;
}

compile as "g++ -pedantic -Wall -std=c++11", and will ouptut

5

Upvotes: 2

Related Questions