1

As @user17732522 pointed out that the deleter of std::unique_ptr is supposed to be callable with exactly one pointer as argument.

How can I retrieve the return value of pclose when the unique_ptr is destroyed?

This code snippet does not compile,

#include<memory>
#include<iostream>
#include<string>
#include<cstdio>

int ExecCmd(const std::string& cmd, std::string& output)
{
    int ret = 0;

    {
        std::unique_ptr<FILE, void(*)(FILE*, int*)> pipe(popen(cmd.c_str(), "r"), [](FILE* file, int* ret_ptr){
                              if(NULL==file)
                              {
                                  *ret_ptr = -1;
                              } 
                              else 
                              {
                                  *ret_ptr = pclose(file);
                              }
                         });
    }

    return ret;
}

int main()
{

}

Whereas this code snippet below compiles.

#include<memory>
#include<iostream>
#include<string>
#include<cstdio>

int ExecCmd(const std::string& cmd, std::string& output)
{
    int ret = 0;

    {
        std::unique_ptr<FILE, void(*)(FILE*)> pipe(popen(cmd.c_str(), "r"), [](FILE* file){
                              if(NULL==file)
                              {
                                 
                              } 
                              else 
                              {
                                    pclose(file);
                              }
                         });
    }

    return ret;
}

int main()
{

}

Here is what the compiler complains about the former code snippet:

In file included from /opt/compiler-explorer/gcc-11.3.0/include/c++/11.3.0/memory:76,
                 from <source>:1:
/opt/compiler-explorer/gcc-11.3.0/include/c++/11.3.0/bits/unique_ptr.h: In instantiation of 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = _IO_FILE; _Dp = void (*)(_IO_FILE*, int*)]':
<source>:20:27:   required from here
/opt/compiler-explorer/gcc-11.3.0/include/c++/11.3.0/bits/unique_ptr.h:357:63: error: static assertion failed: unique_ptr's deleter must be invocable with a pointer
  357 |         static_assert(__is_invocable<deleter_type&, pointer>::value,
      |                                                               ^~~~~
/opt/compiler-explorer/gcc-11.3.0/include/c++/11.3.0/bits/unique_ptr.h:357:63: note: 'std::integral_constant<bool, false>::value' evaluates to false
/opt/compiler-explorer/gcc-11.3.0/include/c++/11.3.0/bits/unique_ptr.h:361:24: error: too few arguments to function
  361 |           get_deleter()(std::move(__ptr));
      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~

I tried this code snippet to acquire the return value, but it does not compile:

#include<memory>
#include<iostream>
#include<string>
#include<cstdio>

int ExecCmd(const std::string& cmd, std::string& output)
{
    int ret = 0;

    {
        std::unique_ptr<FILE, void(*)(FILE*)> pipe(popen(cmd.c_str(), "r"), [&ret](FILE* file){
                              if(NULL==file)
                              {
                                  ret = -1;
                              } 
                              else 
                              {
                                  ret = pclose(file);
                              }
                         });
    }

    return ret;
}

int main()
{

}

Could anybody shed some light on this matter?

John
  • 2,963
  • 11
  • 33
  • Even in the second example, you must still call `pclose`. – Some programmer dude May 25 '22 at 04:59
  • @user17732522 *The deleter is supposed to be callable with exactly one pointer as argument. * Any reference? ***What do you expect to be passed as second argument?*** I hope to acquaire the return value of `pclose`. – John May 25 '22 at 05:00
  • @Someprogrammerdude Yes, I forgot it. – John May 25 '22 at 05:01
  • As for your problem, see e.g. [this `std::unique_ptr` reference](https://en.cppreference.com/w/cpp/memory/unique_ptr) Note that the description text says that "[t]he object is disposed of, using a potentially user-supplied deleter by calling `get_deleter()(ptr)`". Which is also shown in the error message. – Some programmer dude May 25 '22 at 05:03
  • @user17732522 Sorry for my poor English. It's hard for me to express freely. Updated. – John May 25 '22 at 05:09

3 Answers3

7

Since you can only provide a custom deleter which can be callable with exactly one pointer as argument, you can not have the lambda with two arguments in it as deleter.

Your lambda with capture will also not work, as it can not be converted to a pointer to a function (i.e. only stateless lambdas can be converted to free function pointers type)


How can I retrieve the return value of pclose when the std::unique_ptr is destroyed?

Using lambda

int ExecCmd(const std::string& cmd, std::string& output)
{
    int ret = 0;
    const auto deleter = [&ret](FILE* file) { ret = file ? pclose(file) : 0; };
    {
        std::unique_ptr<FILE, decltype(deleter)> pipe(popen(cmd.c_str(), "r"), deleter);
    }
    return ret;
}

See a demo

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • It's a pity that `gcc 4.9` could not compile this [code snippet](https://godbolt.org/z/TdM5a41YG). – John May 25 '22 at 06:46
  • 1
    No need for the nullcheck btw, unique_ptr handles it internally. – alagner May 25 '22 at 06:52
  • @alagner Any reference? – John May 25 '22 at 06:53
  • 1
    @John e.g. this one: [If get() == nullptr there are no effects](https://en.cppreference.com/w/cpp/memory/unique_ptr/%7Eunique_ptr) – alagner May 25 '22 at 06:56
  • @alagner It's a bad news. If I understand you correctly, the deleter of the `std::unique_ptr` would not be called if the `std::unique_ptr::get()` is `nullptr`, so the `ret` could not be assigned to `-1`(`0` is wrong in the said code snippet). How do you think about it? – John May 25 '22 at 07:04
  • 2
    @John simply initialize `ret = -1` instead. It won't be overwritten if the `FILE*` is `nullptr`. – Remy Lebeau May 25 '22 at 07:10
  • @John exactly as you've written. Note that it can always be pre-set to -1 or a more complex object can be utilized to monitor the output. – alagner May 25 '22 at 07:10
  • @alagner ***a more complex object can be utilized to monitor the output.*** How? I really can't see it helps. As you say the deleter would not be called if `std::unique_ptr::get()` is `nullptr`. Could please explain that in more detail? – John May 25 '22 at 07:16
  • @John for example: the int can be replaced with a wrapper that indicates whether/when it was modified. Difficult to answer without a broader context. If `ExecCmd` is more-less close to the way it's used, I'd just set the integer -1 and don't care too much ;) @JeJo it won't hurt, it's simply superfluous and always false ;) – alagner May 25 '22 at 07:20
  • @alagner ***a wrapper that indicates whether/when it was modified***, like `std::optional`? – John May 25 '22 at 07:23
  • @John optional is a good example if its operation is what you want. Another option would involve something that would log/crash/do anything else to indicate its value has not been checked when leaving the scope; plenty of options there, but I cannot tell the use case for you :) – alagner May 25 '22 at 07:29
  • @JeJo Thanks very much. This is the first time I encounter such problem. And It makes dazed that should I never construct lambdas in this way in the future with GCC4.9.3? I created [a new post to make it clear](https://stackoverflow.com/questions/72373561/problem-when-constructing-the-lambdas-with-compiler-of-specific-version). – John May 25 '22 at 07:34
2

As you're not using the output, it can be removed. You'll need to pass ret by reference to the deleter so that it can be modified by the result. Ternary operators can help to reduce if blocks as fp will evaluate to false if it is a null pointer. You can acquire the type of deleter with decltype so that it can be easily passed to the template of std::unique_ptr.

int execCmd(const std::string& cmd) {
    int ret{};
    auto deleter = [&ret](FILE* fp) { ret = fp ? pclose(fp) : 0; };
    {
        std::unique_ptr<FILE, decltype(deleter)> pipe{popen(cmd.c_str(), "r"),
                                                  deleter};
    }
    return ret;
}
Masrur Rahman
  • 31
  • 1
  • 3
  • It's a pity that gcc 4.9 could not compile this [code snippet](https://godbolt.org/z/TdM5a41YG). Do you know why? – John May 25 '22 at 06:54
  • @John If you write the code like this answer and not some fancily modified version of it, it will compile. Hint: use `auto deleter = ...;` instead of `auto deleter{...};`. – j6t May 25 '22 at 07:09
  • @j6t Yep, thanks a lot. You save me an hour. I really curious why `auto deleter = ...;` works, whereas `auto deleter{...};` does not. Could you please explain that in more detail? – John May 25 '22 at 07:12
  • @John Compiler bug? It seems to treat the curly braces as `initializer_list` instead of a uniform initialization. – j6t May 25 '22 at 07:23
  • @jet Thanks a lot. This is the first time I encounter such problem. And It makes dazed that should I never construct lambdas in this way in the future with GCC4.9.3? I created [a new post to make it clear](https://stackoverflow.com/questions/72373561/problem-when-constructing-the-lambdas-with-compiler-of-specific-version). – John May 25 '22 at 07:34
1

Thanks to @Jelo, @Some programmer dude, @user17732522.

For the readers of this post, this code snippet works, too.

#include<memory>
#include<iostream>
#include<string>
#include<stdio.h>
#include<functional>

using std::placeholders::_1;

void ClosePipe(FILE* file, int& ret)
{
    ret = pclose(file);

    return;
}

int ExecCmd(const std::string& cmd, std::string& output)
{
    int ret = -1;

    auto delete_binded = std::bind(ClosePipe, _1, std::ref(ret));

    {
        std::unique_ptr<FILE, decltype(delete_binded) > pipe{popen(cmd.c_str(), "r"),  delete_binded};
    }

    return ret;
}

int main()
{

}
John
  • 2,963
  • 11
  • 33