Please, could someone explain in plain English what is "Extending move semantics to *this"? I am referring to this proposal. All what am looking for is what is that & why do we need that. Note that I do understand what an rvalue reference is in general, upon which move semantics is built. I am not able to grasp what such an extension adds to rvalue references!
The ref-qualifier feature (indicating the type of *this) would allow you to distinguish whether a member function can be called on rvalues or lvalues (or both), and to overload functions based on that. The first version gives some rationale in the informal part:
Prevent surprises:
struct S {
S* operator &() &; // Selected for lvalues only
S& operator=(S const&) &; // Selected for lvalues only
};
int main() {
S* p = &S(); // Error!
S() = S(); // Error!
}
Enable move semantics:
class X {
std::vector<char> data_;
public:
// ...
std::vector<char> const & data() const & { return data_; }
std::vector<char> && data() && { return data_; } //should probably be std::move(data_)
};
X f();
// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move
For example, you can overload operators as free functions with rvalue references if you wish:
Foo operator+(Foo&& a, const Foo& b)
{
a += b;
return std::move(a);
}
To achieve the same effect with a member function, you need the quoted proposal:
Foo Foo::operator+(const Foo& b) && // note the double ampersand
{
*this += b;
return *this;
}
The double ampersand says "this member function can only be called on rvalues".
Whether or not you must explicitly move from *this in such a member function is discussed here.
Related
In the following MCVE, std::assignable_from reports that A<double>& can't be assigned to from A<double> -- when it clearly can.
#include <iostream>
template <typename T>
class A
{
public:
const A& operator= (const A& other) { return *this; }
};
int main ()
{
A<double> d1, d2; d1 = d2; //this works fine
std::cout << std::boolalpha
<< "Is double assignable? " << std::assignable_from<double&, double> << '\n' //Says true, as it should
<< "Is A<double> assignable? " << std::assignable_from<A<double>&, A<double>> << '\n'; //Says false
return 0;
}
I know why. assignable_from expects operator= to have a return type of A&, not const A&.
template <class _LTy, class _RTy>
concept assignable_from = is_lvalue_reference_v<_LTy>
&& common_reference_with<const remove_reference_t<_LTy>&, const remove_reference_t<_RTy>&>
&& requires(_LTy _Left, _RTy&& _Right) {
{ _Left = static_cast<_RTy&&>(_Right) } -> same_as<_LTy>;
};
Is there an alternative, short of writing my own concept for assignability? I've always had = return const &, because I thought it was dumb to say (A=B)=C.
I've always had = return const &, because I thought it was dumb to say (A=B)=C.
You are free to do that, but there are consequences when you break with established conventions. You have encountered one of them. std::assignable_from requires that you follow established C++ conventions for assignment operators. And this includes returning a modifiable reference to the assigned type in your operator= overload.
It should also be noted that if you return a const& from the assignment operator, you cannot =default it. C++ is very serious about returning modifiable references from assignment operators. It's an expected part of the language.
And writing your own concept for assignment won't help when you have to pass your type to a conceptualized function/class that is constrained on std::assignable_from or any constraint that uses it.
So just follow C++'s conventions.
There is indeed a problem with assignment operator and specifically the implicitly defined one, and also the increment and decrement operator as they are implemented, for historical reasons, in the standard library. It looks like the community has forgotten this old debate. You are right to look for a limitation of the assignment operator "reachability", but the way you do it is not the way it used to be advised.
First let's consider the problem:
#include <vector>
std::vector <int> f();
void g(std::vector <int> v) {
decltype (auto) v1 = f() = v; //(1)
decltype (auto) it = ++v.begin(); //(2)
v1. size() //undefined behavior;
++it; //undefined behavior;
}
The problem here is that the assignment operator and the preincrement operator are returning a reference to a temporary materialization. But the lifetime of those temporaries end at the end of their respective full-expressions (1) and (2).
The way to fix the problem is to ref qualify those operators.
#include <concepts>
struct A {
A& operator = (const A&) &
=default;
A& operator = (A&&) &
=default;
//unfortunately, it implies verbosity:
A(const A&) =default;
A(A&&) = default;
A() = default;
A& operator ++ () &;
};
static_assert (std::assignable_from <A&, A>);
static_assert (!std::assignable_from <A, A>);
Scott Meyers in this talk at 44:15, says const Rvalue references are used in c++0x standard library to capture certain overloads which are not supposed to be compilable.
Code snippet to illustrate the above mentioned point would be helpful. Thanks.
One usage that I found useful is to disable temporaries biding to reference members. For example, consider the code below:
struct Foo{};
class X
{
const Foo& _foo;
public:
X(const Foo&&) = delete; // prevents rvalue binding
X(const Foo& foo): _foo(foo){} // lvalue is OK
};
Foo get_Foo()
{
return {};
}
const Foo get_const_Foo()
{
return {};
}
Foo& get_lvalue_Foo()
{
static Foo foo;
return foo;
}
int main()
{
// X x1{get_Foo()}; // does not compile, use of deleted function
// X x2{get_const_Foo()}; // does not compile, use of deleted function
X x3{get_lvalue_Foo()}; // OK
}
You definitely want to disable a rvalue being passed to the constructor of X, since rvalues do not bind to const references via constructor parameters, so you end up with a dangling reference. Why const Foo&& and not simply Foo&&? Because if you use X(Foo&&) = delete;, then if your get_Foo() returns const Foo (which is a bad idea, but nevertheless is seen in actual code), it will bind to X(const Foo&) instead, and you end up with a dangling reference. However, X(const Foo&&) in the code above is a better match for a const Foo rvalue, so we obtain the desired effect of not being able to construct an X with a rvalue.
You may also ask why not defining X(Foo&) instead for the lvalue constructor. Then you won't be able to bind const lvalues. So the best approach is to mark X(const Foo&&) = delete;. Hope this clarifies it.
INTRO
Ref qualifiers : A way to dissambiguate the rl-valuness of the implied object. As a quick example, take the following class
class example
{
int member;
public:
// ...
int& value() &;
// ^
int&& value() &&;
// ^^
int const& value() const&;
// ^
};
The use of this C++11 feature (syntax marked with ^), allows us to control the version of value() that will be called with
l-values
temporaries
const l-values
Practically the ref qualification applies to the classe's *this
Defaulted / Deleted functions : Specify a special member function as having the compiler generated (default) definition or inaccessible (delete). As an example take
struct type {
type(const type&) = delete;
type& operator=(const type&) = delete;
};
The above struct, achieves being non copyable with extremely clear semantics
QUESTIONs
Is it possible / valid to combine these features ?
Which are the cases where it's explicitly forbidden or bad style ?
Is there any use case / pattern for such a combination ? (Eg creating conditional interfaces based rl-valueness quick and easy)
Yes, but there's not much use, as constructors and destructors can't be ref-qualified.
You can ref-qualify assignment operators:
struct S {
S &operator=(const S &) && = default;
};
int main() {
S s;
S() = s; // OK
s = S(); // error - LHS must be an rvalue
}
However, I'm somewhat at a loss to imagine what this would be useful for.
I have a class that 'remembers' a reference to some object (e.g. an integer variable). I can't have it reference a value that's destructed immediately, and I'm looking for a way to protect the users of my class from doing so by accident.
Is an rvalue-reference overload a good way to prevent a temporary to be passed in?
struct HasRef {
int& a;
HasRef(int& a):a(a){}
void foo(){ a=1; }
};
int main(){
int x=5;
HasRef r1(x);
r1.foo(); // works like intended.
HasRef r2(x+4);
r2.foo(); // dereferences the temporary created by x+4
}
Would a private rvalue overload do?
struct HasRef {
int& a;
HasRef( int& a ):a(a){}
void foo(){ a=1; }
private:
HasRef( int&& a );
};
... HasRef r2(x+1); // doesn't compile => problem solved?
Are there any pitfalls I didn't see?
If you have to store a const reference to some instance of type B into your class A, then surely you want to be ensured, that lifetime of A instance will be exceeded by the lifetime of B instance:
B b{};
A a1{b}; // allowed
A a2{B{}}; // should be denied
B const f() { return B{}; } // const result type may make sense for user-defined types
A a3{f()}; // should also be denied!
To make it possible you should explicitly to = delete; all the constructor overloadings, which can accept rvalues (both const && and &&). For this to achieve you should just to = delete; only const && version of constructor.
struct B {};
struct A
{
B const & b;
A(B const & bb) : b(bb) { ; } // accepts only `B const &` and `B &`
A(B const &&) = delete; // prohibits both `B &&` and `B const &&`
};
This approach allows you to prohibit passing to the constructor all kinds of rvalues.
This also works for built-in scalars. For example, double const f() { return 0.01; }, though it cause a warning like:
warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]
it still can has effect if you just = delete; only && version of constructor:
struct A
{
double const & eps;
A(double const & e) : eps(e) {} // binds to `double const &`, `double &` AND ! `double const &&`
A(double &&) = delete; // prohibit to binding only to `double &&`, but not to `double const &&`
};
double const get_eps() { return 0.01; }
A a{0.01}; // hard error
A a{get_eps()}; // no hard error, but it is wrong!
For non-conversion constructors (i.e. non-unary) there is an issue: you may have to provide = delete;-d versions for all the combinatorically possible versions of constructors as follows:
struct A
{
A(B const &, C const &) {}
A(B const &&, C const &&) = delete;
// and also!
A(B const &, C const &&) = delete;
A(B const &&, C const &) = delete;
};
to prohibit mixed-cases like:
B b{};
A a{b, C{}};
Ignoring the fact the code isn't valid and just answering the question about the private overload...
In C++11 I would prefer a deleted function to a private function. It's a bit more explicit that you really can't call it (not even if you're a member or friend of the class.)
N.B. if the deleted constructor is HasRef(int&&)=delete it will not be chosen here:
int i;
HasRef hr(std::forward<const int>(i));
With an argument of type const int&& the HasRef(const int&) constructor would be used, not the HasRef(int&&) one. In this case it would be OK, because i really is an lvalue, but in general that might not be the case, so this might be one of the very rare times when a const rvalue reference is useful:
HasRef(const int&&) = delete;
That shouldn't compile. A good C++ compiler (or really almost any C++ compiler that I've ever seen) will stop that from happening.
I'm guessing you're compiling in MSVS. In that case, turn off language extensions and you should get an error.
Otherwise, not that even marking the reference const extends the lifetime of the temporary until the constructor finishes. After that, you'll refer to an invalid object.
I have this code:
template <class T>
class Something
{
T val;
public:
inline Something() : val() {}
inline Something(T v) : val(v) {}
inline T& get() const { return val; }
inline Something& operator =(const Something& a) { val = a.val; return *this; }
};
typedef Something<int> IntSomething;
typedef Something<const int> ConstIntSomething;
class Other
{
public:
IntSomething some_function()
{
return IntSomething(42);
}
ConstIntSomething some_function() const
{
return ConstIntSomething(42);
}
};
void wtf_func()
{
Other o;
ConstIntSomething s;
s = o.some_function();
}
However, the compiler picks the wrong overload of Other::some_function() in wtf_func() (i.e. the non-const one). How can I fix this? Note that for certain reasons I cannot change the name of Other::some_function().
o is not const-qualified, so the non-const some_function is selected. If you want to select the const-qualified overload, you need to add the const qualifier to o:
Other o;
Other const& oref(o);
ConstIntSomething s;
s = oref.some_function();
When overload resolution occurs, the compiler only looks at the o.some_function() subexpression; it does not look at the context around the function call to decide to pick something else. Further, the return type of a member function is not considered during overload resolution.
Note that it may make more sense for IntSomething to be implicitly convertible to ConstIntSomething, either using an operator ConstIntSomething() overload in IntSomething (less good) or using a non-explicit ConstIntSomething(IntSomething const&) constructor in ConstIntSomething (more good).
It doesn't pick the wrong overload; const-ness is resolved by whether this is const or not. In your case, o is non-const, so the non-const overload is picked.
You can hack this by creating a const-reference to o, e.g.:
const Other &o2 = o;
s = o2.some_function();
But really, you should probably be considering your overloads in Something. For instance, you can't currently do this:
IntSomething x;
ConstIntSomething y;
y = x;
which doesn't sound correct. Why shouldn't you be allowed to take a const ref to a non-const ref?
Your object o needs to be const object for a const function to be called on it. Otherwise the compiler rightly picks up the non const version of the function.
The compiler picks the overload to use based on the constness of the object that will become this. You can make it call the desired version with static_cast: s = static_cast<const Other&>(o.some_function());
You might also want to copy the new behaviour found in the containers of the C++0x standard library. Containers such as vector now have members cbegin() and cend() that return a const_iterator whether the container is const or not unlike begin() and end()
class Other {
// Rest of other
public:
// No overload for non-const
// Even if called with a non const Other, since this member is marked
// const, this will be of type Other const * in all cases and will call
// the const qualified overload of some_function.
ConstIntSomething csome_function() const
{
return some_function();
}
};