The lifetime of a pointer is a completely separate deal than the lifetime of the thing pointed to -- which is why extending the lifetime of a pointer is often a not-helpful thing to do.
I am not worried about the thing pointed to, just a type mismatch in the lambda and now the potential dangling reference grumpy was talking about.
The `FILE*&' is syntax goodness to refer to what is internally almost certainly a `FILE**' by the name of the reference directly instead of dereferencing a pointer. In other words, a `FILE*&' is naturally a `FILE*' in the same way that a 'FILE**' is a `FILE*' when dereferenced. This is simple and required behavior. There is nothing wrong with it.
But FILE* and FILE*& are different types and not compatible. If I take the function template away g++ throws errors:
Code:
#include <stdio.h>
void foo(FILE* fp) {}
void test(void(*pf)(FILE*&)) {}
int main()
{
// error: invalid conversion from 'void (*)(FILE*)' to 'void (*)(FILE*&)' [-fpermissive]
test(foo);
// error: cannot convert 'main()::<lambda(FILE*)>' to 'void (*)(FILE*&)' for argument '1' to 'void test(void (*)(FILE*&))'
test([](FILE* fp) {});
}
My first question is why does this assignment work when bound to a function template and is it safe? Is the function template hiding a bug or is it silently doing the "right thing", whatever that is?
Code:
#include <stdio.h>
#include <functional>
using namespace std;
void foo(FILE* fp) {}
void test(function<void(FILE*&)>) {}
int main()
{
// Compiles without errors!
test(foo);
test([](FILE* fp) {});
}
Binding a `rvalue' from a simple temporary to a constant reference, a mutable `rvalue' reference, and a constant `rvalue' reference are all well defined and have required and guaranteed semantics by the standard. This is relevant to the template but not this use. It is possible, by misusing operator chaining, that your source will do the wrong thing in the face of some `rvalues'. Realistically, you will want to overload the constructor to create a temporary object for containment, as member data, that will do the correct thing.
I think the intention of scoped_sentry is obvious, but what I want to do is generically add RAII to an unmanaged resource. In this code it's FILE* where fclose() needs to be called at the end. Before rvalue references I would do it with an lvalue reference and the wrapping step is manual:
Code:
template <typename T>
class scoped_sentry: boost::noncopyable
{
T& object;
function<void(T&)> cleanup;
public:
scoped_sentry(T& object, function<void(T&)> cleanup)
: object(object), cleanup(cleanup) {}
~scoped_sentry() { cleanup(object); }
};
int main()
{
FILE* in = fopen("test.txt", "r");
scoped_sentry<FILE*> sentry(
in,
[](FILE* fp) { if (fp) fclose(fp); });
int ch;
while ((ch = fgetc(in)) != EOF)
putchar(ch);
}
With rvalue references, I thought I could bring the RAII wrapper closer to the unmanaged resource so that initialization is direct in the constructor and the scoped_sentry object replaces the wrapped object. That way a "dummy" object wouldn't be needed and scoped_sentry could manage the resource completely:
Code:
int main()
{
scoped_sentry<FILE*> in(
fopen("test.txt", "r"),
[](FILE* fp) { if (fp) fclose(fp); });
int ch;
while ((ch = fgetc(in)) != EOF)
putchar(ch);
}
I feel better about taking away the reference member and owning a copy, where the rvalue reference in the constructor keeps the semantics like I want without excessive copying. But I am not confident that this will work generically for any T. I can not think of any cases where an unmanaged resource would be represented by anything except a handle, but I am not sure enough about that to see this as complete:
Code:
template <typename T>
class scoped_sentry: boost::noncopyable
{
T object;
function<void(T)> cleanup;
public:
scoped_sentry(T&& object, function<void(T)> cleanup)
: object(object), cleanup(cleanup) {}
~scoped_sentry() { cleanup(object); }
operator T&() { return object; }
};
What do you think?