7
#include <memory>
#include <iostream>
#include <exception>
#include <curl/curl.h>

class client
{
private:
    std::unique_ptr<CURL, decltype(&psclient::del_curl)> uptr_curl_;

    inline CURL * init_curl()
    {
        CURLcode result = curl_global_init(CURL_GLOBAL_DEFAULT);
        if(result != CURLE_OK)
            throw std::logic_error(curl_easy_strerror(result));
        return curl_easy_init();
    }

    inline void del_curl(CURL * ptr_curl)
    {
        curl_easy_cleanup(ptr_curl);
        curl_global_cleanup();
    }
public:
    inline client()
    : uptr_curl_(init_curl(), &client::del_curl)
    {
    }
}

The compiler keeps complaining No matching constructor for initialization of 'std::unique_ptr<CURL, void (*)(CURL *)>'

It seems to me like the declaration is correct for the deleter template argument. It is a function pointer that returns void and takes a CURL * as an argument. This matches the signature of del_curl.

Is there yet another random rule, unknown to me, in C++ that specifies a requirement for template arguments to non-static member function pointers? If so, why?

Francisco Aguilera
  • 3,099
  • 6
  • 31
  • 57
  • 2
    Change `void del_curl(CURL * ptr_curl);` from a non-static member function to a static member function. – R Sahu Apr 08 '15 at 21:10
  • @FranciscoAguilera because you are trying to bind a pointer-to-function to a pointer-to-member-function, and this doesn't work: `uptr_curl_(init_curl(), &client::del_curl)` – vsoftco Apr 08 '15 at 21:12
  • 2
    @FranciscoAguilera Because your statement "This matches the signature of del_curl" is wrong. It does *not* match. A `void (*)(CURL*)` is not the same as a `void (client::*)(CURL*)`. One is a member (requires a `this`), the other is not. *They're not the same type*. You can all-caps WHY all you want; but that's why, whether you like it or not. – WhozCraig Apr 08 '15 at 21:28
  • @vsoftco Yes, it should work somehow, because I don't want those to be static functions. They are not static. There is not 1 big `curl_init` for all instances of `client` they are unique per client. – Francisco Aguilera Apr 08 '15 at 21:29
  • @WhozCraig It does match. That is not the problem. See my edit. I get the same error with the edit. – Francisco Aguilera Apr 08 '15 at 21:30
  • 2
    No, it really doesn't match, and there is nothing in your question that's actually relevant to templates. A pointer-to-function cannot point to a pointer-to-member-function, that's never been possible outside of templates either, and it's fundamentally impossible. Just take a moment to think of how it could possibly work. You need some instance to call a non-static member function on. You don't have any instance available to call it on, nor any room to keep track of an instance. So you can't call it. I'm quite sure this has been asked before already. Let me see if I can find a good dup. –  Apr 08 '15 at 21:37
  • The reason to make `del_curl` a `static` function is because it doesn't make sense to access the functionality with `x.del_curl(y)`: `x` has nothing to do with the operation you're trying to do. The same is true for `init_curl`. In fact, one could argue that these functions shouldn't even be members of the `client` class anyways, since they have absolutely nothing to do with `client` objects. –  Apr 08 '15 at 21:46

2 Answers2

10

The answer of @R. Sahu is correct imo. However, if you insist of passing a non-static member function deleter, here is a way of doing it using the good old std::bind and std::function:

#include <memory>
#include <iostream>
#include <functional>

class Foo
{
private:
    std::unique_ptr<int, std::function<void(int*)>> _up;
public:
    Foo(): _up(new int[42], std::bind(&Foo::deleter, this, std::placeholders::_1))
    {

    }
    void deleter(int* p)
    {
        delete[] p;
        std::cout << "In deleter" << std::endl;
    }
};

int main()
{
    Foo foo;
}

PS: I just don't like the bind, I wonder if one can improve on that.


With a lambda:

Foo(): _up(new int[42],
               [this](int* p)->void
               {
                   deleter(p);
               }
          ){}
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • You are right, it is correct, for instances where you want to "pass a static member function as a unique_ptr deleter", but my question is "How to pass a non-static member function as a unique_ptr deleter". – Francisco Aguilera Apr 08 '15 at 21:35
  • Upticked. I was about to post quite-literally identical code. Thanks for saving me the trouble. =P – WhozCraig Apr 08 '15 at 21:35
  • 1
    @FranciscoAguilera the `deleter` here is non-static. – vsoftco Apr 08 '15 at 21:36
  • I'll update your post if I find an improved version that doesn't use bind. Actually, one way I can think of could be using a lambda somehow. – Francisco Aguilera Apr 08 '15 at 21:37
  • @FranciscoAguilera yes, or `std::mem_fn`, but couldn't come up with a fast solution. – vsoftco Apr 08 '15 at 21:37
  • @vsoftco That comment was meant to address the first line in your answer. – Francisco Aguilera Apr 08 '15 at 21:37
  • @FranciscoAguilera with a lambda seems a bit tricky, since you need to `decltype(lambda)` in the template, however define the lambda in the constructor (i.e. later). – vsoftco Apr 08 '15 at 21:42
  • @vsoftco: doesn't the lambda automatically convert into a `std::function`? –  Apr 08 '15 at 21:50
  • @Hurkyl The lambda implicitly converts to a `std::function`, however in templates the type must be exactly the same. The type of a lambda is a closure, known only to the compiler, so you need the `decltype`. – vsoftco Apr 08 '15 at 21:51
  • 1
    @vsoftco yeah, identical lambda do not equate to identical closures, so I concur with your assessment of the difficulty. A custom deleter wrapper (which your bind is effectively doing) seems the best alternative. You can do it without `std::bind` by providing the full monte yourself ([see example](http://ideone.com/CTXjZN)), but why bother when you can just bind it. – WhozCraig Apr 08 '15 at 21:53
  • @WhozCraig Indeed. Probably there is a way of setting the deleter at runtime, but seems to be too convoluted when `std::bind` does it just fine. – vsoftco Apr 08 '15 at 21:54
  • @vsoftco: You don't need lambda in the type: you just need the deleter type to be something that the lambda can convert to when you pass it into the constructor. –  Apr 08 '15 at 21:55
  • @Hurkyl ohh I see what you meant, just keep the template `std::function`... – vsoftco Apr 08 '15 at 21:55
  • @Hurkyl, so it will implicitly convert to `std::function` rather than a closure if you explicitly put std::function as the template parameter? – Francisco Aguilera Apr 08 '15 at 21:57
  • @Hurkyl you mean [something like this](http://ideone.com/iSSWAd) ? Never even gave it a thought until you mentioned it, I admit. – WhozCraig Apr 08 '15 at 21:58
  • 1
    @FranciscoAguilera the second argument in the `unique_ptr` constructor will try to convert to whatever the second template is, in this case being a `std::function` allows the conversion of any callable object (including lambda's) – vsoftco Apr 08 '15 at 22:05
  • @vsoftco yeah, I ran into [something similar](http://stackoverflow.com/questions/29359544/boost-thread-pool-bind-errors) before. However, using it the lambda way may look prettier, but you would have the overhead of another function call, unless you inline the other function within the lambda. Something to keep in mind. – Francisco Aguilera Apr 08 '15 at 22:07
  • @FranciscoAguilera I think the lambda can be inlined as good as `std::bind`, since internally both produce functors, don't they? – vsoftco Apr 08 '15 at 22:09
  • With the lambda solution, you could just do `this->deleter` without going through the pointer-to-member thing. –  Apr 08 '15 at 22:18
  • @vsoftco Not really sure, would be interesting if someone did a detailed analysis, including performance tests *hint* *hint* :p Over my head tbh i'm just a beginner. – Francisco Aguilera Apr 08 '15 at 22:18
  • @Hurkyl absolutely right, I don't know what I was thinking about, thanks! – vsoftco Apr 08 '15 at 22:19
  • @Hurkyl, is `this *` captured by default or do you have to explicitly capture `this *`? – Francisco Aguilera Apr 08 '15 at 22:23
  • @FranciscoAguilera it's not, that's why I used `[=]`. You can just capture `[this]` though. – vsoftco Apr 08 '15 at 22:24
2

The second template parameter used in the declaration of uptr_curl_ is void (*)(CURL *)

The type of &client::del_curl is void (CURL::*)(CURL*).

They are not the same. You can change del_curl to a static member function. That will resolve the problem.

Update

You can use a non-static member function with the help of std::function and std::bind.

class client
{
   public:
      client();
   private:
      std::unique_ptr<CURL, std::function<void(CURL *)>> uptr_curl_;
      CURL * init_curl();
      void del_curl(CURL * ptr_curl);
};

client::client() : uptr_curl_(init_curl(),
                              std::bind(&client::del_curl, this, std::placeholders::_1))
{
   // ...
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Indeed, I think it's also interesting to say whether it's possible to use a non-static member function as a deleter. Can you somehow use a `std::function` instead? – vsoftco Apr 08 '15 at 21:19
  • No, you are incorrect. The type of `client::del_curl` is `void (psclient::*)(void *)`. See my edit. – Francisco Aguilera Apr 08 '15 at 21:20
  • There is another reason why you can't pass non-static member functions as deleter arguments, and I want to know WHY. @vsoftco That was the original question. Nobody ever reads until the end. xD – Francisco Aguilera Apr 08 '15 at 21:22
  • 1
    @FranciscoAguilera, is that a statement or is that a hunch? – R Sahu Apr 08 '15 at 21:29
  • @RSahu Because I can see no conceivable reason why that would be a requirement, I want to initialize an instance of curl for that class aka as a member function, not for all instances aka static. – Francisco Aguilera Apr 08 '15 at 21:33