I just wrote some code that blew my mind and wanted to share:
https://godbolt.org/g/9BN4LE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
#include <memory>
void func(int *p)
{
++(*p);
}
template<typename R, typename Arg>
struct Helper final
{
using Arg_t = Arg;
Helper(R (&)(Arg)) noexcept
{
}
Helper(R (*)(Arg)) noexcept
{
}
};
template<auto Func, typename T = typename decltype(Helper(Func))::Arg_t>
struct Caller final
{
using pointer = T;
void operator()(pointer p) const
{
Func(reinterpret_cast<typename decltype(Helper(Func))::Arg_t>(p));
}
};
int main()
{
int var = 1234567890;
{
std::unique_ptr<int, Caller<func>> ptr {&var};
}
return var;
}
|
Function pointers/references have always been able to be arguments to templates, but before C++17 you'd always have to know the return type, calling convention, and argument types in advance. Now, with
template<auto> and class template parameter deduction, we can have arbitrary functions as template parameters without needing to specify any information about the function.
The use case for this specific code is wrapping Windows API handles - for example, the type
std::unique_ptr<HMENU, Caller<DestroyMenu>> would wrap a menu handle. The member type alias
pointer is used by
std::unique_ptr to know what the actual pointer type is (so you don't end up with
HMENU *). The
reinterpret_cast is because sometimes the cleanup function Windows wants you to use takes a different argument type than the actual handle type. Using
std::unique_ptr works great because you only ever need to use
.get(),
.release(), or
.reset() - it's a lot easier than writing our own handle wrapper, so instead it can just be a template alias:
1 2 3
|
template<typename Handle, auto FreeFunc>
using unique_handle = std::unique_ptr<Handle, Caller<FreeFunc, Handle>>;
using menu_handle = unique_handle<HMENU, DestroyMenu>;
|
Previously doing this kind of abstraction would've required a lot more template parameters to be passed (return type, argument type, etc), or passing the cleanup function as a parameter to a function and storing it as a member variable or using type erasure, etc. but now it's as simple as just passing the function as a template parameter and letting the rest be deduced automatically. In the past I would use
std::unique_ptr's support for having a function pointer/reference as the deleter, but then you'd have to remember to always pass the cleanup function to the constructor. Making the cleanup function part of the type is much more convenient.
The best part is that it's a zero-overhead abstraction, so the compiler can completely optimize it as shown in the godbolt demo link.
Just felt like sharing that. Also
std::variant is a life saver for one of my projects, along with
if constexpr. C++17 is awesome.