Wrong overload selection? (rvalue reference)
-
I use VS2010 Express. I will try GCC now.
-
Nexus schrieb:
As far as I know, lvalues like
a
orb
cannot be implicitly bound to rvalue references (I think you needstd::move()
). The table "Binding And Overloading" of this page seems to confirm this thought.Probably I mix something up, but they are bound to rvalue references implicitly like the example shows
Nexus schrieb:
What compiler are you using? Maybe it isn't adapted to the latest standard draft (or the latter has changed again).
I can confirm the behavior with g++ (GCC) 4.5.2
I think the following is amazing:
#include <iostream> template<class T> inline void test(const T& a) { std::cout << "2" << std::endl;} //I thought this one will be selected template<class T> inline void test(T&& a) { std::cout << "1" << std::endl;} //But this is selected instead (I have checked in debugger) inline void test2(const int& a) { std::cout << "2" << std::endl;} //I thought this one will be selected inline void test2(int &&a) { std::cout << "1" << std::endl;} //But this is selected instead (I have checked in debugger) int main(){ int a(0); test(a); test2(a); return 0; }
1
2Cheers,
XSpille
-
XSpille schrieb:
Probably I mix something up, but they are bound to rvalue references implicitly like the example shows
Yes, that's why I am confused. I always thought the whole resource stealing only works on temporary objects (rvalues) and on explicitly moved lvalues. Implicit move-semantics is one of the things that have been criticized a lot at
std::auto_ptr
and improved atstd::unique_ptr
.Maybe we should wait for krümelkacker or another C++0x expert
-
The behaviour you are observing is correct. The overloading paradigm for move semantics doesn't mix well with template argument deduction if applied carelessly. Rvalue references (and the rules around them) are there to serve two purposes: move semantics and perfect forwarding.
overloading paradigm for move semantics (without templates):
void foo(int const&); // #1 void foo(int&&); // #2 void test1() { int i = 1729; foo(i); // --> #1 foo(i+0); // --> #2 }
perfect forwarding:
template<class T> void bar(T&& x) { foo(std::forward<T>(x)); } void test2() { int i = 1729; bar(i); // bar<int&>, T&&=int& --> foo #1 bar(i+0); // bar<int>, T&&=int&& --> foo #2 }
To make perfect forwarding work, there is a special rule about deducing the type T in case it appears as T&& for a parameter type. If the expression is an lvalue, T is deduced to be an lvalue reference, and a non-refernece otherwise (see above).
Your case:
template<class T> void badidea(T const&); // #1 template<class T> void badidea(T &&); // #2 void test3() { int i = 1729; badidea(i); // two candidates: // badidea<int>(int const&) (#1) // badidea<int&>(int&); (#2) // --> #2 (better match, since i is non-const) // Oops! Possible accidental mutation of i in #2 }
Since both a and b in your case are lvalues, T is deduced to be an lvalue reference which renders T&& to be lvalue references as well (due to the reference collapsing rules). Now, I'm not sure what it is that you are trying to do, but you can avoid this special deduction rule by using the "SFINAE trick". But maybe, if you explain the purpose of your code fragment, there is a more elegant solution to the problem.
If it makes you feel any better, I agree that this is an unfortunate gotcha. It would have been much better if the programmer was forced to explicitly enable this deduction rule in case perfect forwarding is intended.
kk
-
THX
-
XSpille schrieb:
#include <iostream> template<class T> inline void test(const T& a) { std::cout << "2" << std::endl;} template<class T> inline void test(T&& a) { std::cout << "1" << std::endl;} inline void test2(const int& a) { std::cout << "2" << std::endl;} inline void test2(int &&a) { std::cout << "1" << std::endl;} int main(){ int a(0); test(a); test2(a); return 0; }
1
2Das ist doch echt mal ein unintuitives Verhalten. Was für ein Mist.
-
Thanks for the explanation, too. The reference collapsing is indeed a bit strange, I'll have to look deeper into it. Apparently there can't exist any C++ feature without pitfalls
-
I am trying to code the Euclidean algorithm which find greatest common divisor of two integers. The integers in my case are long numbers; they can be up to 100 kilobytes each! (In fact I trying to compare Euclidean GCD algorithm with binary GCD algorithm for long numbers, but I present here only Euclidean algorithm for clarity).
So, first I made in-place version of the algorithm:
template<class T> inline void gcdInPlace(T &a, T &b) { //The Euclidean algorithm (in-place version). a, b are positive integers, result is in a while(b) { a %= b; //This operation is realised effectively to not cause memory reallocation std::swap(a, b); //Fast swapping since my integers have move constructor and move assignment operator } }
Then I made functions which return result instead of modifying arguments (they are more convenient). Since my long numbers can take a long time to copy, I decided not to copy those arguments that can be freely modified (rvalues):
template<class T> inline T gcd(T const &a, T const &b) { T t1(a), t2(b); gcdInPlace(t1, t2); return t1; } template<class T> inline T gcd(T &&a, T const &b) { T t(b); gcdInPlace(a, t); return a; } //By the way, what is faster to return here? "a" or "t" ? template<class T> inline T gcd(T const &a, T &&b) { T t(a); gcdInPlace(b, t); return b; } template<class T> inline T gcd(T &&a, T &&b) { gcdInPlace(a, b); return a; }
That is where I stuck, since the last line have put one of my unit tests into endless loop.
-
If your bigint type is move-enabled, I would simply write the gcd function like this:
bigint gcd(bigint a, bigint b) { ... return ...; }
or for a generic implementation
template<class T> T gcd(T a, T b) { ... return ...; }
If your bigint type does not have a move constructor, you will have to provide lots of boiler plate everywhere you want to avoid an extra copy. So, that's not really preferable if you have the option of adding a move ctor to bigint. But if you don't have this option you may implement your gcd like this:
template<class T> struct id { typedef T type; }; template<class T> void gcd_inplace(T &a, T &b); // result will be in a template<class T> inline void gcd3( typename id<T>::type const& a, typename id<T>::type const& b, T & result) { result = a; T temp = b; gcd_inplace(result,temp); } template<class T> inline void gcd3( typename id<T>::type && a, typename id<T>::type const& b, T & result) { swap(a,result); T temp = b; gcd_inplace(result,temp); } template<class T> inline void gcd3( typename id<T>::type const& a, typename id<T>::type && b, T & result) { result = a; gcd_inplace(result,b); } template<class T> inline void gcd3( typename id<T>::type && a, typename id<T>::type && b, T & result) { swap(a,result); gcd_inplace(result,b); }
The trick here is to disable template argument deduction for the first two parameters using id<T>::type instead of T. Then, T will only be deduced according to the third argument and therefore never be deduced to be an lvalue reference.
Obviously, this is quite horrible. You don't want to have to write these things for every function just to take advantage of rvalues. The right place to do that is providing a move ctor and simply use pass-by-value.
kk
-
Perhaps, the following wrapper might help in case of a missing move ctor:
template<class T> class pbv // pass-by-value wrapper for large & non-move-enabled types { T* ptr; bool own; pbv(pbv const&); pbv& operator=(pbv const&); public: pbv(T & lvalue) : ptr(new T(lvalue)), own(true) {} pbv(T && rvalue) : ptr(&rvalue), own(false) {} ~pbv() { if (own) delete ptr; } bool was_lvalue() const { return own; } bool was_rvalue() const { return !own; } operator T const&() const { return *ptr; } operator T &() { return *ptr; } T const& get() const { return *ptr; } T & get() { return *ptr; } };
To be used like this:
void gcd3(pbv<bigint> a, pbv<bigint> b, bigint & result) { swap(a.get(),result); gcd_implace(result,b.get()); }
Here, if an lvalue expression is used as argument, the pbv wrapper will make a copy. If an rvalue expression is used as argument, the pbv wrapper won't make a copy and instead return a reference to the "temporary" object. In both cases it is safe to alter the object the pbv-wrapper refers to.
(untested)