I am writing a macro that when used to wrap a function call like so: macro(function())
, returns a std::optional<T>
, where T is the return value of the function. So far it works, but I run into problems when trying to get it to return a std::optional<bool>
when the function has no return type (i.e. it returns void).
The core of the problem is that the compiler tries to evaluate the scope of a if-constexpr that evaluates to false when the function has no return type.
I tried using two if-constexprs and one with an else. I tried inlining everything and spreading it out a few lines. I tried replacing the compiler-calculated constexpr constants with constant literals. Nothing works so far.
The macro as I have it now looks like this:
#ifndef troy
#define troy(body)[](){\
constexpr auto inner_type_is_void=std::is_same<decltype(body), void>::value;\
using inner_type=std::conditional<inner_type_is_void,bool,decltype(body)>::type;\
try{\
if constexpr (inner_type_is_void) {(body); return std::optional<inner_type>{true};}\
if constexpr (!inner_type_is_void) {return std::optional<inner_type>{(body)};}\
} catch (...){\
return std::optional<inner_type>{};\
}\
}()
#endif
And I use it like so:
{
auto a=troy(f());
if (!a){
std::cout << "fail" << std::endl;
} else{
std::cout << "success: " << *a << std::endl;
}
}
It works when f returns a value, but gives a compiler error when not. This compiler error is: no matching function for call to ‘std::optional<bool>::optional(<brace-enclosed initializer list>)’
on the line where the if-constexpr evaluates to false.
In order to avoid doing instantiation for if constexpr
, you need a context that actually has instantiation: you need some kind of template. Outside of a template, there's no magic power that if constexpr
bestows.
The solution, as always, is to just wrap everything in a lambda. Right now, you're using body
in-line for the actual function body. Instead, you can use the lambda:
[&]() -> decltype(auto) { return body; }
And then wrap the whole thing in an immediately invoked lambda that takes this lambda as an argument:
[](auto f){
// stuff
}([&]() -> decltype(auto) { return body; })
Now, where I marked // stuff
, we're in a template context and we can actually use if constexpr
to avoid instantiations:
[](auto f){
using F = decltype(f);
using R = std::invoke_result_t<F>;
try {
if constexpr (std::is_void_v<R>>) {
f();
return std::optional<bool>(true);
} else {
return std::make_optional(f());
}
} catch (...) {
return std::optional<std::conditional_t<std::is_void_v<R>, bool, R>>();
}
}([&]() -> decltype(auto) { return body; })
Now just add a bunch of \
s and you're good to go.