In C(++), a function can return a value. Or a void.
A function with a return value ends with return X;.
A function that returns void, has a last line return;. Without value. (and you don't have to write that return;, but that doesn't matter in this discussion)
When using templates, this needs consideration, if you allow the return type to be "templated" and you want to support void as one of the options for a return value of a member.
A void function (named a procedure in a lot of programming languages) is different than a function that returns something. Internally and conceptually.
internally: the return value is a position on the stack. void functions don't have that position.
conceptually: ignoring the internally difference, and returning something in a void() function anyways, breaks the C(++) language. Also not returning a value (of the correct type) in a function that does require a return value, fails to compile.
I wrote a templated class that can call a callback. And I wanted it to support returning numbers / bools.
The thing is: when no callback function is set, the class had to work too. It has to return a value in that case .So I wrote this code:
inline R call(Args... args) { if (_callback == nullptr) { return 0; // R can only be a arithmetic type. 0 should work as default. } return (*_callback)(args...); }
This works great for boolean, integer, float return values. But it you want to support callbacks that don't have a return value, it will fail.
If R is type void, the compilation will not allow return 0;. And if R is a number, compilation will not allow return;.
solution: constant expressions
In C++ , we can write constant expressions. A condition that's checked at compile time. If it's true, the containing block will be compiled. If it's false, not.
A bit related to the C style #ifdef , but context aware. A constant expression knows the code it's acting upon (including parameter types). These constant expressions understand C++ source code.
What they have in common with defines: they both are static: they resolve everything during compilation. They have no runtime cost.
I use this construct to have different behaviour if the return type of my callback function is void, and when it's an arithmetic (number) type:
/* * R can either be an arithmetic type, or void */ inline R call(Args... args) { if constexpr (std::is_void<R>::value) { if (_callback == nullptr) { return; } (*_callback)(args...); } if constexpr (! std::is_void<R>::value) { if (_callback == nullptr) { return 0; // R can only be a arithmetic type. 0 should work as default. } return (*_callback)(args...); } }
You can see that when my templated code is configured for a callback function that returns a bool or a number, it 'll use the lower part, and always return a value.
When the template is configured for a void() type of callback (a procedure), it just returns, without a return value.
This compiles, runs, is type safe and has no runtime impact. if constexpr gets resolved when you build the code. If your code doesn't have void() callbacks, the top part doesn't get compiled. If it doesn't have callbacks that expect a return value, the bottom part doesn't get compiled.
Check my set of test cases below, to see how this works:
#include <cstdio> #include "callbackmanager.h" #include <string> class MyClass { public: inline int handler(const int& num1, const int& num2) const { return num1 + num2; } static inline int staticHandler(const int& num1, const int& num2) { return num1 + num2; } }; int functionHandler(const int& num1, const int& num2) { return num1 + num2; } int main() { int a = 4; int b = 5; { // scenario: call object method MyClass myClass; Callback<int, const int&, const int&> cb; // Use a lambda to capture myClass and call the object method cb.set([&myClass](const int& num1, const int& num2) -> int { return myClass.handler(num1, num2); }); int o = cb.call(a, b); printf("Value: %i\n", o); fflush(stdout); } { // scenario: call static method Callback<int, const int&, const int&> cb; // Use a lambda to call the static method cb.set([](const int& num1, const int& num2) -> int { return MyClass::staticHandler(num1, num2); }); int o = cb.call(a, b); printf("Value: %i\n", o); fflush(stdout); } { // scenario: call C function Callback<int, const int&, const int&> cb; // Use a lambda to call the classic C function cb.set([](const int& num1, const int& num2) -> int { return functionHandler(num1, num2); }); int o = cb.call(a, b); printf("Value: %i\n", o); fflush(stdout); } { // scenario: call pure lambda Callback<int, const int&, const int&> cb; // Use a lambda to execute anonymous C code cb.set([](const int& num1, const int& num2) -> int { return num1 + num2; }); int o = cb.call(a, b); printf("Value: %i\n", o); fflush(stdout); } { // scenario: return a bool Callback<bool, const int&, const int&> cb; // Use a lambda to execute anonymous C code cb.set([](const int& num1, const int& num2) -> bool { return num1 == num2; }); printf("Value: %s\n", cb.call(a, b) ? "true" : "false"); fflush(stdout); } { // scenario: use a callback that returns void Callback<void, const int&, const int&> cb; // Use a lambda to execute anonymous C code cb.set([](const int& num1, const int& num2) { printf("void gets num1: %i, num2: %i\n", num1, num2); fflush(stdout); return; }); cb.call(a, b); } { // scenario: use a callback that returns void, and has no attributes Callback<void> cb; // Use a lambda to execute anonymous C code cb.set([]() { printf("void with no parameters\n"); fflush(stdout); return; }); cb.call(); } /* { // scenario: use an unsupported (non-fundamental) type for return value R // this will generate a compile error Callback<std::string, const int&, const int&> cb; } */ }
a constexpr knows "everything" about your code. When you use it in a template class, it will know for what types your project will use that template class. Then it 'll take care that it 'll only send that part of the code to the compiler, that is needed for your project. For classes like the one that I'm writing, this is great. My class has no clue how it 'll be used. But I provide logic for a set of scenarios that I want to support. |