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,
...)
.