I am trying to create a class whose objects must contain a short description ("name") of what their value represent. Therefore the only public constructor should take a string as argument.
For the operations, however, I need to create temporary (no relevant name) object to calculate the value to be assigned to an already existing object. For that I have implemented a private constructor, which should not be used, neither directly nor indirectly, to instantiate a new object - these temporary objects should only be assigned to an already existing object, through operator=, which only copies the value rather than name and value.
The problem comes with the use of "auto". If a new variable is declared as follows:
auto newObj = obj + obj;
the compiler deduces the return type of operator+ and directly assign its result to newObj. This results in an object with a irrelevant name, which should not be possible to instantiate.
Also, deducing the type of an already existing object should still be possible from some functions, like:
auto newObj = obj.makeNewObjWithSameTypeButOtherName("Other name");
Follows a code demonstrating the problem:
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Sample
{
public:
Sample(const string&);
Sample<T> makeNewObj(const string&);
// Invalid constructors
Sample();
Sample(const Sample&);
void operator=(const Sample&);
void operator=(const T&);
Sample<T> operator+(const Sample&) const;
void show(void);
private:
// Private constructor used during operations
Sample(const T&);
T _value;
string _name;
};
template<class T>
Sample<T>::Sample(const string& name)
{
this->_name = name;
this->_value = 0;
}
template<class T>
Sample<T>::Sample(const T&value)
{
this->_name = "Temporary variable";
this->_value = value;
}
template<class T>
Sample<T>
Sample<T>::makeNewObj(const string& name)
{
return Sample<T>(name);
}
template<class T>
void
Sample<T>::operator=(const Sample& si)
{
this->_name = this->_name; // Make explicit: Never change the name
this->_value = si._value;
}
template<class T>
void
Sample<T>::operator=(const T& value)
{
this->_name = this->_name; // Make explicit: Never change the name
this->_value = value;
}
template<class T>
Sample<T>
Sample<T>::operator+(const Sample& si) const
{
// if any of the two values are invalid, throw some error
return Sample<T>( this->_value + si._value );
}
template<class T>
void
Sample<T>::show(void)
{
cout << _name << " = " << _value << endl;
}
int main()
{
Sample<double> a("a"), b("b");
a = 1; // Sample::operator=(const T&)
b = 2.2; // Sample::operator=(const T&)
a.show(); // Output: a = 1
b.show(); // Output: b = 2.2
auto c = a.makeNewObj("c"); // Should be possible
c = a + b; // Sample::operator+(const Sample&) and Sample::operator=(const Sample&)
c.show(); // Output: c = 3.2
// Sample<double> d; // Compiler error as expected: undefined reference to `Sample::Sample()'
// auto f = a; // Compiler error as expected: undefined reference to `Sample::Sample(Sample const&)'
// This is what I want to avoid - should result in compiler error
auto g = a+c; // No compiler error: uses the private constructor Sample::Sample(const T&)
g.show(); // Output: Temporary variable = 4.2 <-- !! Object with irrelevant name
}
A quick workaround is to not return a temporary Sample<T> from operator +. Since you only want the value part you can just return that instead. That changes the code to
T operator+(const Sample&) const;
template<class T>
T
Sample<T>::operator+(const Sample& si) const
{
// if any of the two values are invalid, throw some error
return this->_value + si._value;
}
and then
auto g = a+c;
will make g whatever T is and g.show(); will not compile as g isn't a Sample<T>.
Sample<double> g = a+c;
Will also not work as it tries to construct g from a value and that constructor is private.
This will require adding
friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }
If you want to be able to chain additions like
a + a + a;
Somewhat related but also orthogonal to NathanOliver's answer:
You are mixing different concepts here. You have the notion of, essentially, NamedValue with Sample, but you are trying to make each expression formed out of arithmetics on NamedValue also a NamedValue. That is not going to work - the expression (by your semantics) does not have a name, so it should not be a NamedValue. Therefore, having NamedValue operator+(const NamedValue& other) is not meaningful.
Nathan's answer resolves this by making additions return T instead. That's pretty straightforward.
However, note that since a + b must have a type, you cannot stop auto g = a + b from compiling, even if it is demonstrably incorrect code. Ask Eigen, or any other expression template library. This remains true no matter how you choose the return type of operator+. So this wish of yours cannot be granted, unfortunately.
Still, I would suggest that you don't use plain T as return type but rather another class, say, Unnamed<T>:
template<class T>
class Unnamed
{
public:
explicit Unnamed(const T& value) : _value(value) {};
Unnamed<T> operator+(const Unnamed<T>& rhs) const
{
return Unnamed<T>(_value + rhs._value);
}
friend Unnamed operator+(const Unnamed& lhs, const Sample<T>& rhs);
friend Unnamed operator+(const Sample<T>& lhs, const Unnamed& rhs);
private:
T _value;
};
This allows you to do your checks and what have you on every operation (because the middle + in (a + b) + (c + d) cannot accept NamedValues, see above) instead of only when converting back to a named value.
Demo here.
You can increase the compile-time safety slightly by only allowing construction of Sample from Unnamed temporaries: https://godbolt.org/g/Lpz1m5
This could all be done more elegantly than sketched here. Note that this is moving exactly in the direction of expression templates though.
My suggestion would be to change the signature of the + operator (or any other operation needs implemented) to return a different type.
Than add an assignment operator accepting this "different type", but do not add a copy constructor - alternatively, for better error reporting, add a deleted one.
This would require more coding, since you would probably want to define "operations" on this type as well, so that chaining works.
Related
Imagine you have a number of overloaded methods that (before C++11) looked like this:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
};
This function makes a copy of a (MyBigType), so I want to add an optimization by providing a version of f that moves a instead of copying it.
My problem is that now the number of f overloads will duplicate:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
void f(MyBigType&& a, int id);
void f(MyBigType&& a, string name);
void f(MyBigType&& a, int b, int c, int d);
// ...
};
If I had more parameters that could be moved, it would be unpractical to provide all the overloads.
Has anyone dealt with this issue? Is there a good solution/pattern to solve this problem?
Thanks!
Herb Sutter talks about something similar in a cppcon talk
This can be done but probably shouldn't. You can get the effect out using universal references and templates, but you want to constrain the type to MyBigType and things that are implicitly convertible to MyBigType. With some tmp tricks, you can do this:
class MyClass {
public:
template <typename T>
typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type
f(T&& a, int id);
};
The only template parameter will match against the actual type of the parameter, the enable_if return type disallows incompatible types. I'll take it apart piece by piece
std::is_convertible<T, MyBigType>::value
This compile time expression will evaluate to true if T can be converted implicitly to a MyBigType. For example, if MyBigType were a std::string and T were a char* the expression would be true, but if T were an int it would be false.
typename std::enable_if<..., void>::type // where the ... is the above
this expression will result in void in the case that the is_convertible expression is true. When it's false, the expression will be malformed, so the template will be thrown out.
Inside the body of the function you'll need to use perfect forwarding, if you are planning on copy assigning or move assigning, the body would be something like
{
this->a_ = std::forward<T>(a);
}
Here's a coliru live example with a using MyBigType = std::string. As Herb says, this function can't be virtual and must be implemented in the header. The error messages you get from calling with a wrong type will be pretty rough compared to the non-templated overloads.
Thanks to Barry's comment for this suggestion, to reduce repetition, it's probably a good idea to create a template alias for the SFINAE mechanism. If you declare in your class
template <typename T>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type;
then you could reduce the declarations to
template <typename T>
EnableIfIsMyBigType<T>
f(T&& a, int id);
However, this assumes all of your overloads have a void return type. If the return type differs you could use a two-argument alias instead
template <typename T, typename R>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value,R>::type;
Then declare with the return type specified
template <typename T>
EnableIfIsMyBigType<T, void> // void is the return type
f(T&& a, int id);
The slightly slower option is to take the argument by value. If you do
class MyClass {
public:
void f(MyBigType a, int id) {
this->a_ = std::move(a); // move assignment
}
};
In the case where f is passed an lvalue, it will copy construct a from its argument, then move assign it into this->a_. In the case that f is passed an rvalue, it will move construct a from the argument and then move assign. A live example of this behavior is here. Note that I use -fno-elide-constructors, without that flag, the rvalue cases elides the move construction and only the move assignment takes place.
If the object is expensive to move (std::array for example) this approach will be noticeably slower than the super-optimized first version. Also, consider watching this part of Herb's talk that Chris Drew links to in the comments to understand when it could be slower than using references. If you have a copy of Effective Modern C++ by Scott Meyers, he discusses the ups and downs in item 41.
You may do something like the following.
class MyClass {
public:
void f(MyBigType a, int id) { this->a = std::move(a); /*...*/ }
void f(MyBigType a, string name);
void f(MyBigType a, int b, int c, int d);
// ...
};
You just have an extra move (which may be optimized).
My first thought is that you should change the parameters to pass by value. This covers the existing need to copy, except the copy happens at the call point rather than explicitly in the function. It also allows the parameters to be created by move construction in a move-able context (either unnamed temporaries or by using std::move).
Why you would do that
These extra overloads only make sense, if modifying the function paramers in the implementation of the function really gives you a signigicant performance gain (or some kind of guarantee). This is hardly ever the case except for the case of constructors or assignment operators. Therefore, I would advise you to rethink, whether putting these overloads there is really necessary.
If the implementations are almost identical...
From my experience this modification is simply passing the parameter to another function wrapped in std::move() and the rest of the function is identical to the const & version. In that case you might turn your function into a template of this kind:
template <typename T> void f(T && a, int id);
Then in the function implementation you just replace the std::move(a) operation with std::forward<T>(a) and it should work. You can constrain the parameter type T with std::enable_if, if you like.
In the const ref case: Don't create a temporary, just to to modify it
If in the case of constant references you create a copy of your parameter and then continue the same way the move version works, then you may as well just pass the parameter by value and use the same implementation you used for the move version.
void f( MyBigData a, int id );
This will usually give you the same performance in both cases and you only need one overload and implementation. Lots of plusses!
Significantly different implementations
In case the two implementations differ significantly, there is no generic solution as far as I know. And I believe there can be none. This is also the only case, where doing this really makes sense, if profiling the performance shows you adequate improvements.
You might introduce a mutable object:
#include <memory>
#include <type_traits>
// Mutable
// =======
template <typename T>
class Mutable
{
public:
Mutable(const T& value) : m_ptr(new(m_storage) T(value)) {}
Mutable(T& value) : m_ptr(&value) {}
Mutable(T&& value) : m_ptr(new(m_storage) T(std::move(value))) {}
~Mutable() {
auto storage = reinterpret_cast<T*>(m_storage);
if(m_ptr == storage)
m_ptr->~T();
}
Mutable(const Mutable&) = delete;
Mutable& operator = (const Mutable&) = delete;
const T* operator -> () const { return m_ptr; }
T* operator -> () { return m_ptr; }
const T& operator * () const { return *m_ptr; }
T& operator * () { return *m_ptr; }
private:
T* m_ptr;
char m_storage[sizeof(T)];
};
// Usage
// =====
#include <iostream>
struct X
{
int value = 0;
X() { std::cout << "default\n"; }
X(const X&) { std::cout << "copy\n"; }
X(X&&) { std::cout << "move\n"; }
X& operator = (const X&) { std::cout << "assign copy\n"; return *this; }
X& operator = (X&&) { std::cout << "assign move\n"; return *this; }
~X() { std::cout << "destruct " << value << "\n"; }
};
X make_x() { return X(); }
void fn(Mutable<X>&& x) {
x->value = 1;
}
int main()
{
const X x0;
std::cout << "0:\n";
fn(x0);
std::cout << "1:\n";
X x1;
fn(x1);
std::cout << "2:\n";
fn(make_x());
std::cout << "End\n";
}
This is the critical part of the question:
This function makes a copy of a (MyBigType),
Unfortunately, it is a little ambiguous. We would like to know what is the ultimate target of the data in the parameter. Is it:
1) to be assigned to an object that existing before f was called?
2) or instead, stored in a local variable:
i.e:
void f(??? a, int id) {
this->x = ??? a ???;
...
}
or
void f(??? a, int id) {
MyBigType a_copy = ??? a ???;
...
}
Sometimes, the first version (the assignment) can be done without any copies or moves. If this->x is already long string, and if a is short, then it can efficiently reuse the existing capacity. No copy-construction, and no moves. In short, sometimes assignment can be faster because we can skip the copy contruction.
Anyway, here goes:
template<typename T>
void f(T&& a, int id) {
this->x = std::forward<T>(a); // is assigning
MyBigType local = std::forward<T>(a); // if move/copy constructing
}
If the move version will provide any optimization then the implementation of the move overloaded function and the copy one must be really different. I don't see a way to get around this without providing implementations for both.
In the following example, I have two classes which are the same, except that the first uses a template.
template <typename T>
class Container {
const T t;
public:
Container(const T _t): t(_t) { }
const T get() {
return t;
}
};
class PersonContainer {
const Person& p;
public:
PersonContainer(const Person& _p): p(_p) { }
const Person& get() {
return p;
}
};
Here's the Person class referred to:
struct Person {
int age;
Person(int _age): age(_age) {};
};
When I pass an rValue to a function expecting a lValue, it works fine in the non-template class's constructor (since it's a constant param). However, for the templated classes it fails:
int main() {
PersonContainer personContainer(Person(50)); // works fine
Container<Person&> container(Person(50)); // error
cout << personContainer.get().age;
cout << container.get().age;
return 0;
}
The error message I get is:
no matching function for call to ‘Container<Person&>::Container(Person)’
I'm curious to find out why the templated class does not behave the same way as the regular class.
The problem is how the const binds to T.
What it ends up with is a const reference to T. References are const by default so that makes no difference. Meaning that your constructor expects a T& to which the temporary can't bind(see your other question).
What you were expecting is a reference to a const T.
T = Person&
(const T == Person& const == Person&) != const Person&
I hope this makes sense.
As a reply to your comment on the other answer, you should simply take Person as a template parameter and make your get function return const T&.
In addition, storing references might be dangerous as your "container" might outlive the actual object to which the reference points.
Container<Person&> container(Person(50));
You should remove & from type you are passing to template as it's irrelevant in this context.
Container<Person> container(Person(50));
I came across this piece of code while reading some stuff about vectors in C++11 STL. It uses an assignment of operator=. Now i can't figure out exactly what it does.
Here is the code :
namespace std {
template <typename Allocator> class vector<bool, Allocator> {
public:
// auxiliary proxy type for element modifications:
class reference {
...
public:
reference& operator=(const bool ) noexcept; // assignments
reference& operator=(const reference&) noexcept;
operator bool( ) const noexcept; // automatic type conversion to bool
void flip( ) noexcept;
// bit complement
};
...
// operations for element access return reference proxy instead of bool:
reference operator[]( size_type idx );
reference at( size_type idx );
reference front( );
reference back( );
};
}
From the above code i can understand that it returns a type of class reference. But what i can't understand is this statement reference& operator=(const reference&) noexcept; .
Please let me know what is the actual meaning of this statement in this context
The reference class is what's known as a proxy, it masquerades as another type to give it behaviors you couldn't get otherwise. In this case it's substituting for bool, thus it needs to be assignable to bool. You can see that there are two operator= methods on this class, one that takes bool and one that takes reference&.
One of the rules for operator= is that it must return a reference to the object, so that you can chain the = operators:
b1 = b2 = false;
Use of reference as a class is a poor choice. But...
The function
reference& operator=(const bool ) noexcept;
returns a reference to an object of type reference. Most likely, the return statement in the function looks like:
return *this;
Take a simple class:
struct A
{
A(int in) : data(in) {}
A& operator=(int in)
{
data = in;
return *this;
}
A& operator=(A const& rhs)
{
data = rhs.data;
return *this;
}
};
Usage:
A a(10); // a.data is 10
a = 20; // a.data is 20
A b(5); // b.data is 5
a = b = 35; // a.data as well as b.data are 35
You're right that it returns a reference object (Actually it returns a reference to a reference object - that's what the & means).
The method name indicates that you're overloading the assignment operator =.
The parameter to the method means that it takes an immutable (the const keyword) reference to a reference object.
The noexcept keyword promises that the method will not throw an exception. If you break that promise, it your program will throw a runtime exception.
C++ programmers would interpret this as a copy assignment operator.
The problem
Suppose we implement a string class which represents, uhm, strings. We then want to add an operator+ which concatenates two strings, and decide to implement that via expression templates to avoid multiple allocations when doing str1 + str2 + ... + strN.
The operator will look like this:
stringbuilder<string, string> operator+(const string &a, const string &b)
stringbuilder is a template class, which in turn overloads operator+ and has an implicit string conversion operator. Pretty much the standard textbook exercise:
template<class T, class U> class stringbuilder;
template<> class stringbuilder<string, string> {
stringbuilder(const string &a, const string &b) : a(a), b(b) {};
const string &a;
const string &b;
operator string() const;
// ...
}
// recursive case similar,
// building a stringbuilder<stringbuilder<...>, string>
The above implementation works perfectly as long as someone does
string result = str1 + str2 + ... + strN;
However, it has a subtle bug. Assigning the result to a variable of the right type will make that variable hold references to all the strings that compose the expression. That means, for instance, that changing one of the strings will change the result:
void print(string);
string str1 = "foo";
string str2 = "bar";
right_type result = str1 + str2;
str1 = "fie";
print(result);
This will print fiebar, because of the str1 reference stored inside the expression template. It gets worse:
string f();
right_type result = str1 + f();
print(result); // kaboom
Now the expression template will contain a reference to a destroyed value, crashing your program straight away.
Now what's that right_type? It is of course stringbuilder<stringbuilder<...>, string>, i.e. the type the expression template magic is generating for us.
Now why would one use a hidden type like that? In fact, one doesn't use it explicitely -- but C++11's auto does!
auto result = str1 + str2 + ... + strN; // guess what's going on here?
The question
The bottom line is: it seems that this way of implementing expression templates (by storing cheap references instead of copying values or using shared pointers) gets broken as soon as one tries to store the expression template itself.
Therefore, I'd pretty much like a way of detecting if I'm building a rvalue or a lvalue, and provide different implementations of the expression template depending on whether a rvalue is built (keep references) or a lvalue is built (make copies).
Is there an estabilished design pattern to handle this situation?
The only things I was able to figure out during my research were that
One can overload member functions depending on this being an lvalue or rvalue, i.e.
class C {
void f() &;
void f() &&; // called on temporaries
}
however, it seems I can't do that on constructors as well.
In C++ one cannot really do ``type overloads'', i.e. offer multiple implementations of the same type, depending on how the type is going to be used (instances created as lvalues or rvalues).
I started this in a comment but it was a bit big for that. Then, let's make it an answer (even though it doens't really answer your question).
This is a known issue with auto. For instance, it has been discussed by Herb Sutter here and in more details by Motti Lanzkron here.
As they say, there were discussions in the committee to add operator auto to C++ to tackle this problem. The idea would be instead of (or in addition to) providing
operator string() const;
as you mentioned, one would provide
string operator auto() const;
to be used in type deduction contexts. In this case,
auto result = str1 + str2 + ... + strN;
would not deduce the type of result to be the "right type" but rather the type string because that's what operator auto() returns.
AFAICT this is not going to happen in C++14. C++17 pehaps...
Elaborating on a comment I made to the OP; example:
This only tackles the problem of assigning to either an object or binding to a reference and afterwards converting to a destination type. It is not a comprehensive fix the the problem (also see Yakk's response to my comment), but it prevents the scenario presented in the OP and makes it generally harder to write this kind of error-prone code.
Edit: It might not be possible to expand this approach for class templates (more specifically, the specialization of std::move). Macro'ing could work for this specific problem, but is obviously ugly. Overloading std::move would rely on UB.
#include <utility>
#include <cassert>
// your stringbuilder class
struct wup
{
// only use member functions with rvalue-ref-qualifier
// this way, no lvalues of this class can be used
operator int() &&
{
return 42;
}
};
// specialize `std::move` to "prevent" from converting lvalues to rvalue refs
// (make it much harder and more explicit)
namespace std
{
template<> wup&& move(wup&) noexcept
{
assert(false && "Do not use `auto` with this expression!");
}
// alternatively: no function body -> linker error
}
int main()
{
auto obj = wup{};
auto& lref = obj;
auto const& clref = wup{};
auto&& rref = wup{};
// fail because of conversion operator
int iObj = obj;
int iLref = lref;
int iClref = clref;
int iRref = rref;
int iClref_mv = std::move(clref);
// assert because of move specialization
int iObj_mv = std::move(obj);
int iLref_mv = std::move(lref);
int iRref_mv = std::move(rref);
// works
int i = wup{};
}
Just a wild idea (haven't tried it):
template<class T, class U>
class stringbuilder
{
stringbuilder(stringbuilder const &) = delete;
}
wouldn't force compilation error?
A possible approach would be using the null object pattern. While it might make your string builder bigger, it will still avoid the memory allocations.
template <>
class stringbuilder<std::string,std::string> {
std::string lhs_value;
std::string rhs_value;
const std::string& lhs;
const std::string& rhs;
stringbuilder(const std::string &lhs, const std::string &rhs)
: lhs(lhs), rhs(rhs) {}
stringbuilder(std::string&& lhs, const std::string &rhs)
: lhs_value(std::move(lhs)), lhs(lhs_value), rhs(rhs) {}
stringbuilder(const std::string& lhs, std::string&& rhs)
: rhs_value(std::move(rhs)), lhs(lhs), rhs(rhs_value) {}
stringbuilder(std::string&& lhs, std::string&& rhs)
: lhs_value(std::move(lhs)), rhs_value(std::move(rhs)),
lhs(lhs_value), rhs(rhs_value) {}
//...
If the argument to the constructor is an lvalue, then you store a reference to the real object. If the argument to the constructor is an rvalue, you can move that into an internal variable with almost no cost (move operations are cheap) and store a reference to that internal object. The rest of the code can access the reference knowing (well, at least hoping) that the string will still be alive.
The hoping part is because there is nothing blocking misuse if an lvalue is passed but the object is destroyed before the stringbuilder completes its job.
Here is another attempt at solving the issue of dangling references. It doesn't solve the issue of references to things that are modified though.
The idea is to store the temporaries into values, but to have references to lvalues (that we can expect to keep living after the ;).
// Temporary => store a copy
// Otherwise, store a reference
template <typename T>
using URefUnlessTemporary_t
= std::conditional_t<std::is_rvalue_reference<T&&>::value
, std::decay_t<T>
, T&&>
;
template <typename LHS, typename RHS>
struct StringExpression
{
StringExpression(StringExpression const&) = delete;
StringExpression(StringExpression &&) = default;
constexpr StringExpression(LHS && lhs_, RHS && rhs_)
: lhs(std::forward<LHS>(lhs_))
, rhs(std::forward<RHS>(rhs_))
{ }
explicit operator std::string() const
{
auto const len = size(*this);
std::string res;
res.reserve(len);
append(res, *this);
return res;
}
friend constexpr std::size_t size(StringExpression const& se)
{
return size(se.lhs) + size(se.rhs);
}
friend void append(std::string & s, StringExpression const& se)
{
append(s, se.lhs);
append(s, se.rhs);
}
friend std::ostream & operator<<(std::ostream & os, const StringExpression & se)
{ return os << se.lhs << se.rhs; }
private:
URefUnlessTemporary_t<LHS> lhs;
URefUnlessTemporary_t<RHS> rhs;
};
template <typename LHS, typename RHS>
StringExpression<LHS&&,RHS&&> operator+(LHS && lhs, RHS && rhs)
{
return StringExpression<LHS&&,RHS&&>{std::forward<LHS>(lhs), std::forward<RHS>(rhs) };
}
I've no doubt this could be simplified.
int main ()
{
constexpr static auto c = exp::concatenator{};
{
std::cout << "RVREF\n";
auto r = c + f() + "toto";
std::cout << r << "\n";
std::string s (r);
std::cout << s << "\n";
}
{
std::cout << "\n\nLVREF\n";
std::string str="lvref";
auto r = c + str + "toto";
std::cout << r << "\n";
std::string s (r);
std::cout << s << "\n";
}
{
std::cout << "\n\nCLVREF\n";
std::string const str="clvref";
auto r = c + str + "toto";
std::cout << r << "\n";
std::string s (r);
std::cout << s << "\n";
}
}
NB: I don't provide size(), append() nor concatenator, they aren't the points where the difficulties lie.
PS: I've used C++14 only to simplify the type traits.
Recently I have read that it makes sense when returning by value from a function to qualify the return type const for non-builtin types, e.g.:
const Result operation() {
//..do something..
return Result(..);
}
I am struggling to understand the benefits of this, once the object has been returned surely it's the callers choice to decide if the returned object should be const?
Basically, there's a slight language problem here.
std::string func() {
return "hai";
}
func().push_back('c'); // Perfectly valid, yet non-sensical
Returning const rvalues is an attempt to prevent such behaviour. However, in reality, it does way more harm than good, because now that rvalue references are here, you're just going to prevent move semantics, which sucks, and the above behaviour will probably be prevented by the judicious use of rvalue and lvalue *this overloading. Plus, you'd have to be a bit of a moron to do this anyway.
It is occasionally useful. See this example:
class I
{
public:
I(int i) : value(i) {}
void set(int i) { value = i; }
I operator+(const I& rhs) { return I(value + rhs.value); }
I& operator=(const I& rhs) { value = rhs.value; return *this; }
private:
int value;
};
int main()
{
I a(2), b(3);
(a + b) = 2; // ???
return 0;
}
Note that the value returned by operator+ would normally be considered a temporary. But it's clearly being modified. That's not exactly desired.
If you declare the return type of operator+ as const I, this will fail to compile.
There is no benefit when returning by value. It doesn't make sense.
The only difference is that it prevents people from using it as an lvalue:
class Foo
{
void bar();
};
const Foo foo();
int main()
{
foo().bar(); // Invalid
}
Last year I've discovered another surprising usecase while working on a two-way C++-to-JavaScript bindings.
It requires a combination of following conditions:
You have a copyable and movable class Base.
You have a non-copyable non-movable class Derived deriving from Base.
You really, really do not want an instance of Base inside Derived to be movable as well.
You, however, really want slicing to work for whatever reason.
All classes are actually templates and you want to use template type deduction, so you cannot really use Derived::operator const Base&() or similar tricks instead of public inheritance.
#include <cassert>
#include <iostream>
#include <string>
#include <utility>
// Simple class which can be copied and moved.
template<typename T>
struct Base {
std::string data;
};
template<typename T>
struct Derived : Base<T> {
// Complex class which derives from Base<T> so that type deduction works
// in function calls below. This class also wants to be non-copyable
// and non-movable, so we disable copy and move.
Derived() : Base<T>{"Hello World"} {}
~Derived() {
// As no move is permitted, `data` should be left untouched, right?
assert(this->data == "Hello World");
}
Derived(const Derived&) = delete;
Derived(Derived&&) = delete;
Derived& operator=(const Derived&) = delete;
Derived& operator=(Derived&&) = delete;
};
// assertion fails when the `const` below is commented, wow!
/*const*/ auto create_derived() { return Derived<int>{}; }
// Next two functions hold reference to Base<T>/Derived<T>, so there
// are definitely no copies or moves when they get `create_derived()`
// as a parameter. Temporary materializations only.
template<typename T>
void good_use_1(const Base<T> &) { std::cout << "good_use_1 runs" << std::endl; }
template<typename T>
void good_use_2(const Derived<T> &) { std::cout << "good_use_2 runs" << std::endl; }
// This function actually takes ownership of its argument. If the argument
// was a temporary Derived<T>(), move-slicing happens: Base<T>(Base<T>&&) is invoked,
// modifying Derived<T>::data.
template<typename T>
void oops_use(Base<T>) { std::cout << "bad_use runs" << std::endl; }
int main() {
good_use_1(create_derived());
good_use_2(create_derived());
oops_use(create_derived());
}
The fact that I did not specify the type argument for oops_use<> means that the compiler should be able to deduce it from argument's type, hence the requirement that Base<T> is actually a real base of Derived<T>.
An implicit conversion should happen when calling oops_use(Base<T>). For that, create_derived()'s result is materialized into a temporary Derived<T> value, which is then moved into oops_use's argument by Base<T>(Base<T>&&) move constructor. Hence, the materialized temporary is now moved-from, and the assertion fails.
We cannot delete that move constructor, because it will make Base<T> non-movable. And we cannot really prevent Base<T>&& from binding to Derived<T>&& (unless we explicitly delete Base<T>(Derived<T>&&), which should be done for all derived classes).
So, the only resolution without Base modification here is to make create_derived() return const Derived<T>, so that oops_use's argument's constructor cannot move from the materialized temporary.
I like this example because not only it compiles both with and without const without any undefined behaviour, it behaves differently with and without const, and the correct behavior actually happens with const only.