I had a templated class that had a getter function and a setter function pointer. When I created an object of this class and I didn't want its setter to do anything I'd just pass nullptr
as the setter function argument. Then at runtime, it would check if its setter pointer was nullptr
to see whether it should call the setter.
Using a function pointer isn't good enough now because I want the class to be able to store a lambda. So instead of a function pointer the setter is now a template type argument. Before I used to pass a nullptr to signify that there was no setter, but now I can't, so I use a dummy char member, as you can see:
template <typename GetFunctionType, typename SetFunctionType = char>
class MyClass
{
public:
MyClass(GetFunctionType getFunction, SetFunctionType setFunction = char(0))
: getFunction(getFunction), setFunction(setFunction)
{}
GetFunctionType getFunction;
SetFunctionType setFunction;
typedef decltype(getFunction()) ReturnType;
void set(ReturnType value)
{
if constexpr (std::is_invocable_v<decltype(setFunction), ReturnType>)
{
setFunction(value);
std::cout << "Setter called\n";
}
else
{
// ELSE DO NOTHING
std::cout << "Object has no setter\n";
}
}
};
int main()
{
MyClass foo([]() { return 7; }, [](int val) { std::cout << "You have set the value\n"; });
MyClass foo2([]() {return 7; }); // THIS OBJECT HAS NO SETTER, BUT HAS A CHAR
// MEMBER THAT I USED AS A DUMMY
foo.set(1);
foo2.set(1);
}
My question is do I need that dummy char in cases where the object has no setter function?
Using a function pointer isn't good enough now because I want the class to be able to store a lambda!
Not completely true!
You could store the capture-less lambdas to the typed-function pointers.
See [expr.prim.lambda.closure] (sec 7)
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator.
In addition to that, normally a getter would have a signature
ReturnType /*const-ref*/ <function>();
similarly, the setter would have
void <function>(ArgumentType) /*const*/; // where ReturnType == ArgumentType usually
Combining these two information, I am suggesting the following re-structuring of your class.
#include <iostream>
#include <string>
template <typename ArgType>
class MyClass final
{
// alias types
using GetFunctionType = ArgType(*)(void);
using SetFunctionType = void(*)(ArgType);
GetFunctionType getFunction;
SetFunctionType setFunction;
public:
// Now you can set the function pointer to by default `nullptr`
MyClass(GetFunctionType getFunction = nullptr, SetFunctionType setFunction = nullptr)
: getFunction{getFunction}
, setFunction{setFunction}
{}
void set(ArgType value) const noexcept
{
if (getFunction && setFunction) // runtime nullptr check
{
setFunction(value);
std::cout << "Setter called\n\n\n";
} else {
std::cout << "Object has no setter\n\n";
}
}
};
int main()
{
// mention the argument type
MyClass<int> foo(
[]() { return 7; },
[](int val) { std::cout << "You have set the value: " << val << "\n"; }
);
MyClass<std::string> foo2([]() {return std::string{}; }); // also works
foo.set(1);
foo2.set("String");
}
It would be good if there was a way you could pass
void
or 'something
' so that when you declare a class member like this:T setFunction
; the compiler just removes it from the class.
To my understanding, when you do the partial specialization , you do not even need to declare the setFunction
at all.
Following is the example code, in which the first specialization
template <typename Getter, typename Setter>
class MyClass final{};
handles the when you provide Getter
and Setter
cases, whereas the second one handles the no setter situation.
template <typename Getter>
class MyClass<Getter, std::nullptr_t> final
Unfortunately, you still need to specify the second argument (ie std::nullptr_t{}
) in order to choose the correct specialization.
#include <iostream>
#include <cstddef> // std::nullptr_t
template <typename Getter, typename Setter>
class MyClass final
{
Getter mGetFunction;
Setter mSetFunction;
using ReType = decltype(mGetFunction());
static_assert(std::is_invocable_v<decltype(mGetFunction)>, " Getter is not callable!");
static_assert(std::is_invocable_v<decltype(mSetFunction), ReType>, " Setter is not callable!");
public:
MyClass(Getter mGetFunction, Setter mSetFunction) noexcept
: mGetFunction{ mGetFunction }
, mSetFunction{ mSetFunction }
{}
void set(ReType value) const noexcept {
mSetFunction(value);
std::cout << "Setter called\n";
}
};
template <typename Getter>
class MyClass<Getter, std::nullptr_t> final
{
Getter mGetFunction;
using ReType = decltype(mGetFunction());
static_assert(std::is_invocable_v<decltype(mGetFunction)>, " Getter is not callable!");
public:
MyClass(Getter mGetFunction, std::nullptr_t) noexcept
: mGetFunction{ mGetFunction }
{}
void set(ReType value) const noexcept {
std::cout << "Object has no setter\n";
}
};
int main()
{
MyClass foo{
[]() { return 7; },
[](int val) { std::cout << "You have set the value\n"; }
};
foo.set(1);
//unfortunately, needed to pass second argument for class instantiation
MyClass foo2([]() {return 7; }, std::nullptr_t{});
foo2.set(1);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.