I'm currently using a C library that defines a number of data types, all of which need to have their lifetimes managed by the user. There are a number of functions defined in this fashion:
int* create() {
return new int();
}
void destroy(int* i) {
delete i;
}
Most of these don't need to be accessed after creation. They simply need to exist. Because of this, I'm trying to manage them using unique_ptr
's declared in the scope of which I need them to live.
This is how such a declaration looks like:
// Note that I'm avoiding writing the type's name manually.
auto a = std::unique_ptr<std::decay_t<decltype(*create())>, decltype(&destroy)>{create(), &destroy};
But this is overly verbose, so I encapsulated it in a utility function template:
template<typename T>
auto make_unique_ptr(T* p, void (*f)(T*)) {
return std::unique_ptr<T, decltype(f)>(p, f);
}
Which is used this way:
auto b = make_unique_ptr(create(), &destroy);
This looks nice, but introduces a non-standard function that has no real purpose other than being syntactic sugar for some declarations. My colleagues may not even know it exists and end up creating other versions of it with different names.
c++17 Introduced class template argument deduction. I thought this is the perfect solution to my problem: A standard way to deduce all those types without resorting to user-defined wrappers. So I tried this:
auto c = std::unique_ptr{create(), &destroy};
As is the rule with C++ compilers and templates, this fails with a several lines long error message. Here are the relevant parts:
(...): error: class template argument deduction failed:
auto c = std::unique_ptr{create(), &destroy};
^
(...): note: candidate: 'template<class _Tp, class _Dp>
unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer, typename std::remove_reference<_Dp>::type&&)-> std::unique_ptr<_Tp, _Dp>'
unique_ptr(pointer __p,
^~~~~~~~~~
(...): note: template argument deduction/substitution failed:
(...): note: couldn't deduce template parameter '_Tp'
auto c = std::unique_ptr{create(), &destroy};
^
Theoretically, I could add a deduction guide to handle this:
namespace std {
template<typename T>
unique_ptr(T* p, void (*f)(T*)) -> unique_ptr<T, decltype(f)>;
}
And it does work at least on my version of gcc, but the standard doesn't like it very much:
[namespace.std]
1 Unless otherwise specified, the behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std.
4 The behavior of a C++ program is undefined if it declares
(...)
4.4 - a deduction guide for any standard library class template.
There's also some problems related to differentiating between pointers and arrays, but let's ignore that.
Finally, the question(s): Is there any other way to 'help' std::unique_ptr
(or maybe std::make_unique
) to deduce the correct type when using custom deleters? Just in case this is an XY problem, is there any solutions I didn't think of for lifetime management of those types (perhaps std::shared_ptr
)? If both of those answers are a negative, is there any improvements on c++20 I should be looking forward to, that solve this issue?