Reputation: 13453
I was trying to implement an std::unique_ptr
factory that I could use like this:
auto fd = my_make_unique<fclose>(fopen("filename", "r"));
I.e., pass the deleter function as a template argument.
My best attempt in C++11 was:
template<typename D, D deleter, typename P>
struct Deleter {
void operator()(P* ptr) {
deleter(ptr);
}
};
template<typename D, D deleter, typename P>
std::unique_ptr<P, Deleter<D, deleter, P>> my_make_unique(P* ptr)
{
return std::unique_ptr<P, Deleter<D, deleter, P>>(ptr);
}
In C++14 it is much cleaner:
template<typename D, D deleter, typename P>
auto my_make_unique(P* ptr)
{
struct Deleter {
void operator()(P* ptr) {
deleter(ptr);
}
};
return std::unique_ptr<P, Deleter>(ptr);
}
But both solutuions would require me to pass the type of &fclose
before fclose
itself as template argument:
auto fd = my_make_unique<decltype(&fclose), fclose>(fopen("filename", "r"));
Is it possible to get rid of decltype(&fclose)
template argument in C++11? What about in C++14?
EDIT: Why this question is not a duplicate of RAII and smart pointers in C++: referenced question is about general RAII techniques in C++, and one of the answers estates that std::unique_ptr
can be used for this purpose. I am already familiar with RAII pattern and how std::unique_ptr
is a solution, but I am concerned with present question on how to build an easier to use abstraction to this frequent case I encounter when interacting with C libraries.
Upvotes: 6
Views: 1122
Reputation: 303107
Is it possible to get rid of
decltype(&fclose)
template argument in C++11? What about in C++14?
No, not until C++17 can you get rid of the type of that parameter. Template non-type parameters need a type, which you can't deduce - because it has to be a template non-type parameter. That's one problem.
Additionally, you have the problem that taking the address of functions in the standard library is unspecified. The standard library is always allowed to provide additional overloads, for instance, so &fclose
may be invalid. The only truly portable way of doing this is to provide a lambda or write your own wrapper function:
auto my_fclose_lam = [](std::FILE* f) { std::fclose(f); }
void my_fclose_fun(std::FILE* f) { std::fclose(f); }
And with either of those, with C++14 at best you can introduce a macro like:
#define DECL(v) decltype(v), v
auto fd = my_make_unique<DECL(my_fclose_lam)>(fopen("filename", "r"));
C++17 allows you to at least lift your custom function into a template parameter (though not yet a lambda) via template auto
:
template <auto deleter, typename P>
auto my_make_unique(P* ptr)
{
struct Deleter {
void operator()(P* ptr) {
deleter(ptr);
}
};
return std::unique_ptr<P, Deleter>(ptr);
}
my_make_unique<my_fclose_fun>(fopen(...));
C++20 will finally allow you to stick a lambda into there:
my_make_unique<[](std::FILE* f){ std::fclose(f); }>(fopen(...));
Old incorrect answer:
So the best you can do is introduce a macro like:
#define DECL(v) decltype(v), v
auto fd = my_make_unique<DECL(&fclose)>(fopen("filename", "r"));
Whether or not you think that's a good idea is probably up to your coworkers.
In C++17, with template auto
, you could just be able to write my_make_unique<fclose>
, which is great:
template <auto deleter, typename P>
auto my_make_unique(P* ptr)
{
struct Deleter {
void operator()(P* ptr) {
deleter(ptr);
}
};
return std::unique_ptr<P, Deleter>(ptr);
}
Upvotes: 5
Reputation: 2115
Another workaround is use exactly signature of function:
template<typename T, int (*P)(T*)> //for `fclose`
auto my_make_unique(T*) { ... }
template<typename T, void (*P)(T*)> //for other function signatures
auto my_make_unique(T*) { ... }
//etc.
auto uniq = my_make_unique<File, fclose>(fopen("filename", "r"));
This is not universal solution but in 95% cases will work.
Upvotes: 2
Reputation: 596417
The typical way to create a std::unique_ptr
for a FILE*
pointer is:
auto fd = std::unique_ptr<FILE, decltype(fclose)>(fopen(...), fclose);
You could wrap that in a macro:
#define my_make_unique(ptr, deleter) \
std::unique_ptr<std::remove_pointer<decltype<ptr>>::type, d>(ptr, deleter)
And then use it like this:
auto fd = my_make_unique(fopen(...), fclose);
Upvotes: 0
Reputation: 27766
Pragmatic approach: Make the deleter a runtime parameter.
template<typename P, typename D>
auto my_make_unique(P* ptr, D deleter)
{
return std::unique_ptr<P, D>(ptr, deleter);
}
int main()
{
auto fd = my_make_unique(fopen("filename", "r"), fclose);
}
Upvotes: 4