6

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.

Community
  • 1
  • 1
lvella
  • 12,754
  • 11
  • 54
  • 106
  • `FILE*` was just an example everyone is acquainted to. This is most useful to interact with C libraries full of `create_()` and `destroy_()` functions. Anyway, `*` and `->` are available for plain `FILE *` pointers, too, and it is no worse inside `std::unique_ptr`. – lvella Apr 12 '17 at 19:45
  • I disagree with this duplication mark, my question is much more specific. – lvella Apr 12 '17 at 19:51
  • Most voted answer in this other questions shows usage `RAIIFunc f = ::open("...");`, which, as my solution, also requires the type of the destructor function as the first template argument. My question is specifically how to remove that template argument (if possible). – lvella Apr 12 '17 at 19:57
  • Taking the address of a standard library function is undefined. thus passing fclose as a template argument is undefined behavior (even so it works), because a standard library implementation is free to add additional overlaods or more parameters with default arguments, only the calls are specified to work (with some exceptions) see https://wg21.link/p0551 R>=3) – PeterSom Apr 09 '18 at 14:42

4 Answers4

5

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

Barry
  • 286,269
  • 29
  • 621
  • 977
  • see my comment to the question, taking the address of fclose is non-portable and not sanctioned by the C++ standard (will be more explicit verboten in C++2a) – PeterSom Apr 09 '18 at 14:43
4

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);     
}
zett42
  • 25,437
  • 3
  • 35
  • 72
  • This is what I am currently doing. The drawback, besides the possibly larger structure, is that the return type (that I typedef to something) is not default constructible. – lvella Apr 12 '17 at 20:07
  • see above. &fclose is verboten. – PeterSom Apr 09 '18 at 14:44
  • @PeterSom What do you want to tell me? – zett42 Apr 09 '18 at 16:17
  • It is unspecified to take the address of a standard library function. That includes the functions inherited from the C library, such as fclose. To make this answer correct, fclose must not be given directly as an argument. One workaround would be a lambda calling fclose and passing that. – PeterSom Apr 13 '18 at 11:42
  • @PeterSom I see. I will update this answer when I find some time. – zett42 Apr 13 '18 at 15:03
2

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.

Yankes
  • 1,958
  • 19
  • 20
0

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);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770