lvella
lvella

Reputation: 13453

Wrapper to build std::unique_ptr with plain function deleters

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

Answers (4)

Barry
Barry

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

Yankes
Yankes

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

Remy Lebeau
Remy Lebeau

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

zett42
zett42

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

Related Questions