I want to write decorator functions for callable objects.
This is what I have now:
#include <utility>
template <typename DecoratedT, typename CallableT>
constexpr auto before_callable(DecoratedT &&decorated, CallableT &&callBefore)
{
return [decorated = std::forward<DecoratedT>(decorated),
callBefore = std::forward<CallableT>(callBefore)](auto &&...args){
callBefore(std::as_const(args)...); // TODO: ignore parameters?
auto &&res = decorated(std::forward<decltype(args)>(args)...);
return res;
};
}
template <typename DecoratedT, typename CallableT>
constexpr auto after_callable(DecoratedT &&decorated, CallableT &&callAfter)
{
return [decorated = std::forward<DecoratedT>(decorated),
callAfter = std::forward<CallableT>(callAfter)](auto &&...args){
auto &&res = decorated(std::forward<decltype(args)>(args)...);
callAfter(std::as_const(args)...); // TODO: ignore parameters?
return res;
};
}
template <typename DecoratedT, typename CallBeforeT, typename CallAfterT>
constexpr auto decorate_callable(DecoratedT &&decorated,
CallBeforeT &&callBefore,
CallAfterT &&callAfter)
{
return before_callable(after_callable(std::forward<DecoratedT>(decorated),
std::forward<CallAfterT>(callAfter)),
std::forward<CallBeforeT>(callBefore));
}
This code works while a decorated
object has a return type which is not void
. Error otherwise:
<source>:21:24: error: forming reference to void
21 | auto &&res = decorated(std::forward<decltype(args)>(args)...);
| ^~~
#include <iostream>
template <typename SumT>
void print(const SumT& sum)
{
const auto &res = sum(4, 811);
std::cout << res << std::endl;
}
int main()
{
struct {
int operator()(int lhs, int rhs) const
{
std::cout << "summing\n";
return lhs rhs;
}
} sum{};
const auto &printBefore = [](const int lhs, const int rhs, const auto &...){
std::cout << "Before sum (args): " << lhs << " " << rhs << std::endl;
};
const auto &printAfter = [](const auto &...){
std::cout << "After sum" << std::endl;
};
std::cout << "Undecorated: ";
print(sum);
std::cout << "\nDecorated Before:\n";
print(before_callable(sum, printBefore));
std::cout << "\nDecorated After:\n";
print(after_callable(sum, printAfter));
std::cout << "\nDecorated Before and After:\n";
print(decorate_callable(sum, printBefore, printAfter));
struct {
void operator()() const {}
} retVoid{};
const auto &voidDecorated = decorate_callable(retVoid,
[](const auto &...){},
[](const auto &...){});
// voidDecorated(); // does not compile
return 0;
}
https://godbolt.org/z/x94ehTq17
- What is a simple and optimization-friendly way of handling
void
return type from adecorated
object? - How can I optionally ignore arguments for lambdas where I don't need them? Using variadic lambdas is a one way, but it is enforced on the user side.
- Should I use
decltype(auto)
for return in function declarations?
CodePudding user response:
I'm using the SCOPE_EXIT macro from Andrei Alexandrescus talk about Declarative Control Flow. The trick here is that SCOPE_EXIT creates an object with a lambda (the following block) and executes the lambda in the destructor. This delays the execution until the control flow exits the block.
SCOPE_EXIT will always execute the code, SCOPE_SUCCESS will only execute the code if no exception is thrown and SCOPE_FAIL will execute the code only when an exception is thrown.
Note: the original decorator didn't execute the callAfter when an exception is thrown.
#include <utility>
#include <exception>
#ifndef CONCATENATE_IMPL
#define CONCATENATE_IMPL(s1, s2) s1##s2
#endif
#ifndef CONCATENATE
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#endif
#ifndef ANONYMOUS_VARIABLE
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#endif
template <typename FunctionType>
class ScopeGuard {
FunctionType function_;
public:
explicit ScopeGuard(const FunctionType& fn) : function_(fn) { }
explicit ScopeGuard(const FunctionType&& fn) : function_(std::move(fn)) { }
~ScopeGuard() noexcept {
function_();
}
};
enum class ScopeGuardOnExit { };
template <typename Fun>
ScopeGuard<Fun> operator (ScopeGuardOnExit, Fun&& fn) {
return ScopeGuard<Fun>(std::forward<Fun>(fn));
}
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ScopeGuardOnExit() [&]()
template <typename DecoratedT, typename CallableT>
constexpr auto after_callable(DecoratedT &&decorated, CallableT &&callAfter)
{
return [decorated = std::forward<DecoratedT>(decorated),
callAfter = std::forward<CallableT>(callAfter)](auto &&...args){
SCOPE_EXIT {
callAfter(std::as_const(args)...); // TODO: ignore parameters?
};
auto &&res = decorated(std::forward<decltype(args)>(args)...);
return res;
};
}
CodePudding user response:
A naive way is to check return type with if constexpr
template<typename DecoratedT, typename CallableT>
constexpr decltype(auto) before_callable(DecoratedT &&decorated, CallableT &&callBefore)
{
return [decorated = std::forward<DecoratedT>(decorated),
callBefore = std::forward<CallableT>(callBefore)](auto &&...args) {
callBefore(std::as_const(args)...); // TODO: ignore parameters?
return decorated(std::forward<decltype(args)>(args)...);
};
}
template<typename DecoratedT, typename CallableT>
constexpr decltype(auto) after_callable(DecoratedT &&decorated, CallableT &&callAfter)
{
return [decorated = std::forward<DecoratedT>(decorated),
callAfter = std::forward<CallableT>(callAfter)](auto &&...args) {
constexpr auto ret_type_is_void =
std::is_void_v<decltype(decorated(std::forward<decltype(args)>(args)...))>;
if constexpr (!ret_type_is_void)
{
auto &&res = decorated(std::forward<decltype(args)>(args)...);
callAfter(std::as_const(args)...); // TODO: ignore parameters?
return res;
}
else
{
decorated(std::forward<decltype(args)>(args)...);
callAfter(std::as_const(args)...); // TODO: ignore parameters?
}
};
}