Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 379
lvella Avatar asked Apr 12 '17 19:04

lvella


1 Answers

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);
}

like image 53
Barry Avatar answered Sep 19 '22 00:09

Barry