Home > OS >  How to pass in a callable object into a template class's constructor, able to call it later?
How to pass in a callable object into a template class's constructor, able to call it later?

Time:07-12

I want a class template whose constructor accepts (among other things), a callable argument. The class can then store a reference/pointer to this callable object and later call the function. I'll try to sketch out what I'm looking for here:

template <typename T>
class MyClass {
public:
  MyClass(T _a, Callable& _f)
  : a(_a)
  , f(_f)
  {}

  float getFloat() {
    return f(a);
  }

private:
  T a;
  Callable f;
};

static float minus1(int num) {
  return num - 1;
}

class Stateful {
public:
  Stateful()
  : num_calls(0)
  {}

  float operator()(int num) {
      num_calls;
    return static_cast<float>(num) - (static_cast<float>(num_calls) * 0.5);
  }
private:
  int num_calls;
};

static std::function<float(float)> invert = [](float a){ return -a; };


MyClass<int> option1(-5, &minus1);
MyClass<int> option1a(99, &minus1);
option1.getFloat(); // -6
option1a.getFloat(); // 98

static Stateful stateful{};
MyClass<int> option2(10, &stateful);
option2.getFloat(); // 9.5
option2.getFloat(); // 9

MyClass<int> option2a(100, &stateful);
option2a.getFloat(); // 98.5
option2.getFloat(); // 8

MyClass<float> option3(1.602, &invert);
MyClass<float> option3a(-6.022, &invert);
option3a.getFloat(); // 6.022
option3.getFloat(); // -1.602

float pi = 3.14f;
MyClass<bool> option4(true, [&pi](bool b){return (b ? pi : 0.f);};
option4.getFloat(); // -3.14

I know I can solve this somewhat with some classical inheritance, i.e. use some BaseCallable subclass in MyClass and have all client Callable types inherit from that subclass. However, I don't like this because it would be nice to be able to pass in a lambda, or a std::function, into MyClass.

I tried using Callable as a template, but I don't like this approach because I am using a variant so that I can have a container of MyClass:

using Element = variant<MyClass<int>, MyClass<float>, MyClass<bool>>;
vector<Element> vec;
...

and I think the idea is unworkable if there is another template parameter there, especially with lambda types.

I've tried implementing Callable as a type erasure Concept, which I think is the best way to go here but I can't seem to get it to work without throwing exceptions due to the internal shared_ptr of f being nullptr in the getFloat() call. Any help here would really be greatly appreciated!

EDIT to add how the vector is used:

using Element = variant<MyClass<int>, MyClass<float>, MyClass<bool>>;
vector<Element> vec;

vec.push_back(option1);
vec.push_back(option2);
vec.push_back(option2a);
vec.push_back(option3);
vec.push_back(option3a);
vec.push_back(option4);

for (auto& option : vec) {
  std::visit([](auto&& arg) { std::cout << arg.getFloat() << std::endl; }, option);
}

EDIT2: final working version following @aschepler's answer: https://godbolt.org/z/MbdK8o891

CodePudding user response:

I've tried implementing Callable as a type erasure Concept

That's a good idea, but the implementation has already been done for you. Use the type std::function<float(T)> as your Callable. The template argument to std::function is a function type, written in general as ReturnType(ParamType1, ParamType2,...).

See std::function on cppreference.com.

  • Related