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.
Related
I'm writing a pointer class and overloading the dereference operator operator*, which returns a reference to the pointed-to object. When the pointed-to type is not void this is fine, but we cannot create a reference to void, so I'm trying to disable the operator* using a requires clause when the pointed-to type is void.
However, I'm still getting compiler errors from GCC, Clang, and MSVC for the void case even though it does not satisfy the requires clause.
Here is a minimal example and compiler explorer link (https://godbolt.org/z/xbo5v3d1E).
#include <iostream>
#include <type_traits>
template <class T>
struct MyPtr {
T* p;
T& operator*() requires(!std::is_void_v<T>)
{
return *p;
}
};
int main() {
int x = 42;
MyPtr<int> i_ptr{&x};
*i_ptr = 41;
MyPtr<void> v_ptr{&x};
std::cout << *static_cast<int*>(v_ptr.p) << '\n';
std::cout << x << '\n';
return 0;
}
And here is the error (in Clang):
<source>:7:6: error: cannot form a reference to 'void'
T& operator*()
^
<source>:20:17: note: in instantiation of template class 'MyPtr<void>' requested here
MyPtr<void> v_ptr{&x};
^
1 error generated.
ASM generation compiler returned: 1
<source>:7:6: error: cannot form a reference to 'void'
T& operator*()
^
<source>:20:17: note: in instantiation of template class 'MyPtr<void>' requested here
MyPtr<void> v_ptr{&x};
^
1 error generated.
Execution build compiler returned: 1
However, if I change the return type of operator* from T& to auto&, then it works in all 3 compilers. If I use trailing return type auto ... -> T& I also get errors in all 3 compilers.
Is this a triple compiler bug, user error, or is this intended behavior?
The requires clause doesn't matter because T is a parameter of the class template. Once T is known, the class can be instantiated, but if T is void, that instantiation fails because of the member function signature.
You can either put that requires on the entire class, or make the member function a template like this:
template<typename U = T>
U& operator*() requires(!std::is_void_v<U> && std::is_same_v<T, U>)
{
return *p;
}
Demo
Making the return type auto& is almost the same thing: the return type is deduced by replacing auto with an imaginary type template parameter U and then performing template argument deduction. Note that the version above with requires makes the compilation error clear if you try to use this function with U=void: GCC says template argument deduction/substitution failed: constraints not satisfied.
I don't think there is a way to reproduce exactly what an auto& return type does by making the function a template. Something like this might come close:
template<typename U = T>
std::enable_if_t<!std::is_void_v<T>, U>& operator*()
{
return *p;
}
Compare what you're trying with the equivalent using std::enable_if (without concepts):
template<std::enable_if_t<!std::is_void_v<T>, bool> = true>
T& operator*()
{
return *p;
}
This will give you an error like no type named 'type' in 'struct std::enable_if<false, bool>', because SFINAE wouldn't work in this situation where T is not a parameter of the function template.
Technically, you can also change the return type depending on whether T is void, but this is probably a bad idea:
using R = std::conditional_t<std::is_void_v<T>, int, T>;
R& operator*()
{
// calling this with T=void will fail to compile
// 'void*' is not a pointer-to-object type
return *p;
}
In addition to the Nelfeal's answer, let me give an alternative solution. The problem is not in the dependence of requires condition on T, but is in the return type T&. Let's use a helper type trait:
std::add_lvalue_reference_t<T> operator*()
requires(!std::is_void_v<T>)
{
...
}
It works because std::add_lvalue_reference_t<void> = void, which makes operator*() signature valid for T = void.
The program below generates a compiler error:
MSVC: error C2782: 'double dot(const V &,const V &)': template parameter 'V' is ambiguous
GCC: deduced conflicting types for parameter 'const V' ('Matrix<3, 1>' and 'UnitVector')
I had thought that it would not have this problem because the constructor UnitVector(Vector) is marked explicit, and therefore the arguments (of the call to dot()) can only resolve as Vector with implicit conversions. Can you tell me what I am misunderstanding? Does the compiler consider explicit constructors as implicit conversions when resolving template parameters?
template<int M, int N>
struct Matrix {
};
using Vector = Matrix<3,1>;
struct UnitVector : Vector{
UnitVector(){}
explicit UnitVector(const Vector& v)
{}
operator const Vector&(){
return *static_cast<const Vector*>(this);
}
};
template<typename V>
double dot(const V& a, const V& b){
return 0.0;
}
int main()
{
dot(Vector(),UnitVector());
}
No, that does not work. But actually, you don't need the template
double dot(const Vector &, const Vector &) {...}
works. You don't even need the conversion operator defined in UnitVector. Child to base conversions are done implicitly.
If you want to generally take two types that are implicitly convertible to a common type, the following should work (untested)
template<class U>
double dot_impl(const U&, const U&) {...}
template<class U, class V>
auto dot(const U &u, const V &v) {
return dot_impl<std::common_type_t<U, V>>(u, v);
}
Since the template parameter is explicit, the implicit conversions to thw common type of those two are done in the call, so everything works nice. I moved the original dot to dot_impl, since otherwise we would call dot with one template parameter, which could still be ambiguous.
i have a class with a overladed operators.
class sout {
public:
template<typename T>
friend inline sout& operator&(sout&, T&);
friend inline sout& operator&(sout&, std::string&);
};
now if i use the templated operator& inside the the sting.operator& i get an error:
code:
sout& operator&(sout& s, std::string& str) {
uint16_t ts = static_cast<uint16_t>(str.size()); // this is ok
s & ts; // is also ok
s & static_cast<uint16_t>(str.size()); // but this is wrong
// ...
return s;
}
error:
Error:C2679: binary '&' : no operator found which takes a right-hand operand of type 'uint16_t' (or there is no acceptable conversion)
could be 'sout &operator &<uint16_t>(sout &,T &)'
with
[
T=uint16_t
]
or 'sout &operator &(sout &,std::string &)'
while trying to match the argument list '(sout, uint16_t)'
than i tried to use the explicite operator& template-type by:
operator&<uint16_t>(s, ts); // this also ig ok
but if i combine it, i again a error:
operator&<uint16_t>(s, static_cast<uint16_t>(str.size())
error:
'operator &' : cannot convert parameter 2 from 'uint16_t' to 'uint16_t &'
i also tried reinterpret_cast.
i know operator& is expecting a reference to uint16_t and the size() function is returning a size_t (int) not a reference. is it possible to do that in one line?
The problem is that the value returned by size() is a temporary, and temporaries are rvalues; however, your function accepts an lvalue reference. The following snippet clarifies the problem:
int foo() { return 42; }
void bar(int& i) { i++; } // Parameter is an lvalue reference to non-const
int main()
{
bar(foo()); // ERROR! Passing an rvalue to bar()
bar(1729); // ERROR! Passing an rvalue to bar()
int i = 42;
bar(i); // OK! Passing an lvalue to bar()
}
lvalue references cannot bind to rvalues, unless they are references to const.
template<typename T>
friend inline sout& operator&(sout&, T const&);
// ^^^^^
If your operator& is supposed to modify the right hand argument, so that the reference cannot be a reference to const, in C++11 you may use rvalue references (this will allow to bind to lvalues as well due to C++11's reference collapsing rules):
template<typename T>
friend inline sout& operator&(sout&, T&&);
// ^^^
I'm tracking down a C++ compiler error which I can't figure out. I've reduced it to this code:
#include <boost/config.hpp>
#include <boost/type_traits/remove_reference.hpp>
template<typename T> inline const T bar( T in )
{
typedef BOOST_DEDUCED_TYPENAME boost::remove_reference<T>::type nonref;
const nonref* inPtr = ∈
return *inPtr;
}
class Foo
{
};
int main()
{
Foo foo;
const Foo& ref = bar< Foo& >( foo );
}
which results in:
tt.cpp: In function ‘const T bar(T) [with T = Foo&]’:
tt.cpp:19:39: instantiated from here
tt.cpp:9:13: error: invalid initialization of reference of type ‘Foo&’ from expression of type ‘const nonref {aka const Foo}’
What's the actual issue here? Why is the const missing in the return value? I need the remove_reference since the actual code requires it.
Applying const to a reference type does nothing. You need to make the template argument const foo &, or else remove the reference and then add back both const and the reference in the function signature itself.
See also When should I use remove_reference and add_reference? particularly the second paragraph.
Using VisualStudio 2008 I get the following error
error C2440: 'return' : cannot convert from 'const nonref' to 'Foo &'
Changing bat to
template<typename T> inline const typename boost::remove_reference<T>::type& bar( T in )
{
typedef BOOST_DEDUCED_TYPENAME boost::remove_reference<T>::type nonref;
const nonref* inPtr = ∈
return *inPtr;
}
fixes this and the code compiles.
Returning const T from your function doesn't do what you are expecting. From your code I understand that you expect it to return const Foo&, which is a reference to an immutable object of type Foo.
But when T is Foo&, the expression const T means an immutable reference to an object of type Foo. Since the references are always immutable, the const part is just dropped (according to the paragraph 8.3.2 of the spec)! That is, your function returns Foo& and not const Foo& and that's what the compiler tries to tell you.
#include<iostream>
template<class T>
struct Foo
{
T v_;
Foo(T&& v):v_(std::forward<T>(v))
{
std::cout << "building Foo..\n";
}
};
int main()
{
int v;
Foo<int> foo(v);
std::cin.ignore();
}
visual c++ 2010 output :
error C2664: 'Foo<T>::Foo(T &&)' : cannot convert parameter 1 from 'int' to 'int &&'
Is it normal that I can't bind a lvalue to a rvalue reference ?
EDIT:
same thing for a function :
void f(int && v)
{
}
int v;
f(v); //won't compile
I'm a little bit confused because I think std::forward is usefull to detect if a method is called with a lvalue or a rvalue reference.
But it seems that we can't bind any lvalue into rvalue reference... at least without template parameter but I don't understand it very well
It's normal that calling a move constructor requires an r-value, not an l-value, since an l-value could be named after the move constructor runs, and using a moved-from value would evoke unintended behavior.