I'm trying to write a smart pointer that can easily upcast, but I'm running into trouble with upcasting r-value references. Consider the following:
#include <utility>
template<typename T>
class SmartPtr
{
T* impl_;
public:
// ...
template<typename U>
operator SmartPtr<U>&&() &&
{
U* u = impl_; // Fail with a nice error message if T* isn't implicitly convertable to U*;
return reinterpret_cast<SmartPtr<U>&&>(*this);
}
// ...
};
struct A {};
struct B : public A {};
int main()
{
SmartPtr<B> pb;
SmartPtr<A>&& pa = std::move(pb);
}
This fails in Visual C++ 2015 with the following error message:
error C2440: 'initializing': cannot convert from 'SmartPtr<B>' to 'SmartPtr<A> &&'
note: Reason: cannot convert from 'SmartPtr<B>' to 'SmartPtr<A>'
note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
Note: this works in Clang 3.4 and simpler versions (e.g. a wrapper class being implicitly convertible to an r-value-ref of the inner value) work in Visual Studio 2015.
Should what I'm trying to do work according to the standard? Is this a bug in Visual Studio 2015?
Related
This is a minimized part of Pointer to implementation code:
template<typename T>
class PImpl {
private:
T* m;
public:
template<typename A1>
PImpl(A1& a1) : m(new T(a1)) {
}
};
struct A{
struct AImpl;
PImpl<AImpl> me;
A();
};
struct A::AImpl{
const A* ppub;
AImpl(const A* ppub)
:ppub(ppub){}
};
A::A():me(this){}
A a;
int main (int, char**){
return 0;
}
It compilable on G++4.8 and prior and works as well. But G++4.9.2 compiler raise following errors:
prog.cpp: In constructor 'A::A()':
prog.cpp:24:15: error: no matching function for call to 'PImpl<A::AImpl>::PImpl(A*)'
A::A():me(this){}
^
prog.cpp:24:15: note: candidates are:
prog.cpp:9:5: note: PImpl<T>::PImpl(A1&) [with A1 = A*; T = A::AImpl]
> PImpl(A1& a1) : m(new T(a1)) {
^
prog.cpp:9:5: note: no known conversion for argument 1 from 'A*' to 'A*&'
prog.cpp:2:7: note: PImpl<A::AImpl>::PImpl(const PImpl<A::AImpl>&)
class PImpl {
^
prog.cpp:2:7: note: no known conversion for argument 1 from 'A*' to 'const PImpl<A::AImpl>&'
But it can be fixed by small hack. If i pass '&*this' instead of 'this' then it bring to compilable state.
Is it G++ regression or new C++ standards feature which eliminate backward compatibility?
We can make a simpler example that compiles on neither g++ 4.9 nor clang:
template <typename T>
void call(T& ) { }
struct A {
void foo() { call(this); }
};
int main()
{
A().foo();
}
That is because this is, from the standard, [class.this] (ยง9.3.2):
In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value
is the address of the object for which the function is called.
You cannot take an lvalue reference to a prvalue, hence the error - which gcc explains better than clang in this case:
error: invalid initialization of non-const reference of type A*& from an rvalue of type A*
If we rewrite call to either take a const T& or a T&&, both compilers accept the code.
This didn't compile for me with gcc-4.6, so it seems that gcc-4.8 is where the regression occurred. It seems what you want is to take A1 by universal reference, ie: PImpl(A1 && a1). This compiles for me with both gcc-4.6, gcc-4.8 and gcc-4.9.
template<typename T>
void f(const T &v = T());
template<>
void f<std::string>(const std::string &v)
{
std::cout << v;
}
int main(int argc, char* argv[])
{
f<std::string>(); // Error in VS2013, OK in VS2012, gcc-4.7
f<std::string>("Test"); // OK
f<std::string>(std::string()); //OK
return 0;
}
The latest Visual Studio 2013 compiler gives the following compiler error for the case when the default argument must be used:
error C2440: 'default argument' : cannot convert from 'const std::string *' to 'const std::string &'
Reason: cannot convert from 'const std::string *' to 'const std::string'
No constructor could take the source type, or constructor overload resolution was ambiguous
Visual Studio 2012 and gcc-4.7 compile fine.
Update: As it seems to be a VS2013 bug, are there any temporary workarounds that do not require significant code changes until this is fixed by MS? Bug report was submitted on MS connect.
Whenever I see this kind of problems with template functions, I try to switch to template structures (if you need a temporary workaround)...
template<typename T>
struct foo
{
static void f(const T &v = T());
};
template<>
struct foo<std::string>
{
static void f(const std::string &v = std::string())
{
std::cout << v;
}
};
Unfortunately, I can't check this in Visual Studio 2013 because I don't have it, but I hope it should work.
The downside here is that you should explicitly specify your type, it's no longer deducted
foo<std::string>::f()
foo<std::string>::f("Text")
My wild guess here would be adding like a wrapper function:
template<typename T>
void f_wrapper(const T &v = T())
{
foo<T>::f(v);
}
When I compile the following code with MSVC++, I get an error:
struct A
{
template<typename T>
void operator<<(T&& x)
{
}
};
void f()
{
}
int main()
{
A().operator<<( f ); // ok
A() << f; // error
return 0;
}
g++ and clang both compile this code fine.
AFAIK, 'ok' and 'error' lines do exactly the same thing, and type T is deduced to void(&)(). Or is it void() and rvalue references to function are allowed? If so, what is their meaning?
Is it ok to pass functions by reference like that? Is it MSVC++ bug that it fails to compile 'error' line? BTW, the error output:
no operator found which takes a right-hand operand of type 'overloaded-function' (or there is no acceptable conversion)
could be 'void A::operator <<<void(void)>(T (__cdecl &&))'
with[ T=void (void) ]
Why void operator<<(T&& x)? void operator<<(T& x) serves the purpose.
Function can be called with x() inside overloaded function as below
struct A
{
template<typename T>
void operator<<(T& x)
{
x();
}
};
void f()
{
}
int main()
{
A().operator<<( f );
A() << f;
return 0;
}
So, answering my own question:
The code provided is valid and while rvalue references to functions are allowed (they act identical to lvalue references), here during template deduction T should become void(&)().
A bug in MSVC prevents my code from compiling.
UPDATE: The bug has been fixed in Visual Studio 2013 compiler
Template compilation error using pointer of pointer: (What causes the code not to compile ??)
template <int DIM> class Interface { };
template <int DIM> class Implementation : public Interface<DIM> { };
template <int DIM> class Modifier {
public: void modify(const Interface<DIM>** elem) { } // only one * compiles !!
};
template <int DIM> class Caller {
Modifier<DIM> modifier;
Implementation<DIM>** impl; // only one * compiles !!
public: void call() { modifier.modify(impl); }
};
void main() {
Caller<-13> caller;
caller.call(); // also compiles with this line commented
}
Gives this compilation error (on Visual Studio 1988 professional):
imlgeometry.cpp(-10) : error C2664: 'Modifier<DIM>::modify' : cannot convert parameter 1 from 'Implementation<DIM> **' to 'const Interface<DIM> **'
with
[
DIM=-23
]
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
imlgeometry.cpp(-16) : while compiling class template member function 'void Caller<DIM>::call(void)'
with
[
DIM=-29
]
imlgeometry.cpp(-17) : see reference to class template instantiation 'Caller<DIM>' being compiled
with
[
DIM=-34
]
The problem is that in C++ it is not legal (or safe) to convert a T** to a const T**. The reason is that if you could do this, you would end up being able to subvert const. For example:
const T value;
T* mutablePtr;
const T** doublePtr = &mutablePtr; // Illegal, you'll see why.
*doublePtr = &value; // Legal, both sides have type const int*.
// However, mutablePtr now points at value!
*mutablePtr = 0; // Just indirectly modified a const value!
To fix this, you'll need to update your code so that you aren't trying to do this conversion. For example, you might change the parameter type of modify to be
const Interface<DIM> * const *
Since it is legal to convert T** to const T* const*.
Hope this helps!
Given GMan's deliciously evil auto_cast utility function concocted here, I've been trying to figure out why it doesn't compile for me when I'm trying to auto_cast from an rvalue (on MSVC 10.0).
Here's the code that I'm using:
template <typename T>
class auto_cast_wrapper : boost::noncopyable
{
public:
template <typename R>
friend auto_cast_wrapper<R> auto_cast(R&& pX);
template <typename U>
operator U() const
{
return static_cast<U>( std::forward<T>(mX) );
}
private:
//error C2440: 'initializing': cannot convert from 'float' to 'float &&'
auto_cast_wrapper(T&& pX) : mX(pX) { }
T&& mX;
};
template <typename R>
auto_cast_wrapper<R> auto_cast(R&& pX)
{
return auto_cast_wrapper<R>( std::forward<R>(pX) );
}
int main()
{
int c = auto_cast( 5.0f ); // from an rvalue
}
To the best of my ability I've tried to follow the C++0x reference collapsing rules and the template argument deduction rules outlined here, and as far as I can tell the code given above should work.
Recall that in pre-0x C++, it is not allowed to take a reference to a reference: something like A& & causes a compile error. C++0x, by contrast, introduces the following reference collapsing rules:
A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
The second rule is a special template argument deduction rule for function templates that take an argument by rvalue reference to a template argument:
template<typename T>
void foo(T&&);
Here, the following rules apply:
When foo is called on an lvalue of type A, then T resolves to A& and hence, by the reference collapsing rules above, the argument type effectively becomes A&.
When foo is called on an rvalue of type A, then T resolves to A, and hence the argument type becomes A&&.
Now when I mouse over the call to auto_cast( 5.0f ), the tooltip correctly displays its return value as auto_cast_wrapper<float>. This meaning that the compiler has correctly followed rule 2:
When foo is called on an rvalue of type A, then T resolves to A.
So since we have an auto_cast_wrapper<float>, the constructor should instantiate to take a float&&. But the error message seems to imply that it instantiates to take a float by value.
Here's the full error message, showing again that T=float correctly yet the T&& parameter becomes T?
main.cpp(17): error C2440: 'initializing' : cannot convert from 'float' to 'float &&'
You cannot bind an lvalue to an rvalue reference
main.cpp(17) : while compiling class template member function 'auto_cast_wrapper<T>::auto_cast_wrapper(T &&)'
with
[
T=float
]
main.cpp(33) : see reference to class template instantiation 'auto_cast_wrapper<T>' being compiled
with
[
T=float
]
Any thoughts?
You forgot to std::forward the T&& argument to the auto_cast_wrapper constructor. This breaks the forwarding chain. The compiler now gives a warning but it seems to work fine.
template <typename T>
class auto_cast_wrapper
{
public:
template <typename R>
friend auto_cast_wrapper<R> auto_cast(R&& pX);
template <typename U>
operator U() const
{
return static_cast<U>( std::forward<T>(mX) );
}
private:
//error C2440: 'initializing': cannot convert from 'float' to 'float &&'
auto_cast_wrapper(T&& pX) : mX(std::forward<T>(pX)) { }
auto_cast_wrapper(const auto_cast_wrapper&);
auto_cast_wrapper& operator=(const auto_cast_wrapper&);
T&& mX;
};
template <typename R>
auto_cast_wrapper<R> auto_cast(R&& pX)
{
return auto_cast_wrapper<R>( std::forward<R>(pX) );
}
float func() {
return 5.0f;
}
int main()
{
int c = auto_cast( func() ); // from an rvalue
int cvar = auto_cast( 5.0f );
std::cout << c << "\n" << cvar << "\n";
std::cin.get();
}
Prints a pair of fives.
Sorry for posting untested code. :)
DeadMG is correct that the argument should be forwarded as well. I believe the warning is false and the MSVC has a bug. Consider from the call:
auto_cast(T()); // where T is some type
T() will live to the end of the full expression, which means the auto_cast function, the auto_cast_wrapper's constructor, and the user-defined conversion are all referencing a still valid object.
(Since the wrapper can't do anything but convert or destruct, it cannot outlive the value that was passed into auto_cast.)
I fix might be to make the member just a T. You'll be making a copy/move instead of casting the original object directly, though. But maybe with compiler optimization it goes away.
And no, the forwarding is not superfluous. It maintains the value category of what we're automatically converting:
struct foo
{
foo(int&) { /* lvalue */ }
foo(int&&) { /* rvalue */ }
};
int x = 5;
foo f = auto_cast(x); // lvalue
foo g = auto_cast(7); // rvalue
And if I'm not mistaken the conversion operator shouldn't be (certainly doesn't need to be) marked const.
The reason it doesn't compile is the same reason for why this doesn't compile:
float rvalue() { return 5.0f }
float&& a = rvalue();
float&& b = a; // error C2440: 'initializing' : cannot convert from 'float' to 'float &&'
As a is itself an lvalue it cannot be bound to b. In the auto_cast_wrapper constructor we should have used std::forward<T> on the argument again to fix this. Note that we can just use std::move(a) in the specific example above, but this wouldn't cover generic code that should work with lvalues too. So the auto_cast_wrapper constructor now becomes:
template <typename T>
class auto_cast_wrapper : boost::noncopyable
{
public:
...
private:
auto_cast_wrapper(T&& pX) : mX( std::forward<T>(pX) ) { }
T&& mX;
};
Unfortunately it seems that this now exhibits undefined behaviour. I get the following warning:
warning C4413: 'auto_cast_wrapper::mX' : reference member is initialized to a temporary that doesn't persist after the constructor exits
It appears that the literal goes out of scope before the conversion operator can be fired. Though this might be just a compiler bug with MSVC 10.0. From GMan's answer, the lifetime of a temporary should live until the end of the full expression.