Make callback accept temporary on VS 2010
I have a callback implementation using rvalue references to store arguments that works fine with gcc, but fails to compile in VS 2010 on some code. A short version:
#include <iostream>
#include <string>
class B {
public:
virtual void execute() = 0;
};
template<typename FuncType, typename ArgType>
class F : public B {
public:
F(FuncType func, ArgType && arg) : f(func), arg(arg) {}
void ex开发者_开发知识库ecute() { f(arg); }
private:
FuncType f;
ArgType && arg;
};
template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
return new F<FuncType, ArgType>(func, arg);
}
void myFunction(std::string text)
{
std::cout << "Function " << text << " here" << std::endl;
}
int main()
{
const char text1[] = "sample1";
std::string text2("sample2");
B * b = registerFunc(myFunction, text1);
b->execute();
delete b;
b = registerFunc(myFunction, text2);
b->execute();
delete b;
// VS 2010 fails because of this call
b = registerFunc(myFunction, text2.c_str());
b->execute();
delete b;
return 0;
}
With gcc 4.4 this produces:
$ g++ clbck.cpp -std=c++0x -o clbck && ./clbck
Function sample1 here Function sample2 here Function sample2 here
However it fails to compile in VS 2010 when trying to instantiate registerFunc because of the marked line:
error C2664: 'F::F(FuncType,ArgType &&)' : cannot convert parameter 2 from 'const char *' to 'const char *&&'
with [ FuncType=void (__cdecl *)(std::string), ArgType=const char * ] You cannot bind an lvalue to an rvalue reference
Googling discovered a similar error with Boost 1.44 on VS2010, but the recommended solution there is not to use rvalue references at all. Is there really no other way?
And while you're at it, is there something wrong with the way I'm handling these callbacks? It works fine with function pointers and functors (I have yet to test lambdas), the only downside I found is the one described above. (Bear in mind the code shown here is just a small demonstration, in the actual code I don't give user any pointers; I'm actually using this to execute functions in different threads in a Qt application).
I believe that Visual Studio is right to complain, and you can find an explanation here.
In registerFunc
, the expression arg
is an lvalue
(it has a name). If you want to forward it as an rvalue reference, you have to use std::forward
:
template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
return new F<FuncType, ArgType>(func, std::forward<ArgType>(arg));
}
The same issue occurs in FuncType
constructor, but adding an std::forward
there yields an interesting warning :
reference member is initialized to a temporary that doesn't persist after the constructor exits
Unfortunately, I don't enough of rvalue references to help you further, but I doubt that an rvalue reference member makes any sense : rvalue references bind to objects that are about to die, would it make any sense to store that ?
It's not exactly clear what you hope to achieve with respect to the rvalue reference member. It just doesn't look right. You should avoid storing a reference. You can still use things like std::ref and std::cref if you want the function object to store a reference-like object (much like std::bind behaves).
The issue you ran into is that it's not allowed to initialize an rvalue reference with an lvalue of the target type. But you try to do this because the constructor's parameter "arg" is a named rvalue reference which makes it an lvalue expression in the initializer list. You probably used an old GCC version to compile this code. Newer versions will also complain about this.
You can save yourself some trouble by relying on std::bind:
template<class FuncType>
class F : public B {
public:
explicit F(FuncType func)
: f(std::forward<FuncType>(func))
{}
void execute() { f(); }
private:
FuncType f;
};
....
auto fun = std::bind(myFunction,text2.c_str());
B* ptr = new F<decltype(fun)>(fun);
If you really want to deal with the parameter binding yourself, you should do it like this:
template<class FuncType, class ParamType>
class F : public B {
public:
F(FuncType func, ParamType para)
: f(std::forward<FuncType>(func))
, p(std::forward<ParamType>(para))
void execute() { f(p); }
private:
FuncType f;
ParamType p;
};
Keep in mind that the parameter type T&& where T can be deduced has a special meaning. It's a "catch-all" parameter. Template argument deduction will make T an lvalue reference (and T&& as well by the reference collapsing rules) in case the argument was an lvalue. So, if you always want the parameter to be stored as a copy you have to write
template<class FuncType, class ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
typedef typename std::decay<ArgType>::type datype;
return new F<FuncType,datype>(func, std::forward<ArgType>(arg));
}
I would prefer a registerFunc function like this. It copies the function object and parameter object by default. Overriding this can be done via std::ref and std::cref:
registerFunc(some_func,std::cref(some_obj));
Why not just use lambda expressions?
class B {
virtual void execute() = 0;
};
template<typename T> class F : public B {
T t;
public:
F(T&& arg) : t(std::forward<T>(arg)) {}
void execute() { return t(); }
};
template<typename T> F<T> make_f(T&& t) {
return F<T>(std::forward<T>(t));
}
int main() {
std::string var;
B* callback = make_f([=]() {
std::cout << var << std::endl;
});
}
Or, indeed, std::function<void()>
, which is what it's for.
精彩评论