What are the use cases for having a function return by const value for non-builtin type?
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.
Purpose of returning by const value?
In the hypothetical situation where you could perform a potentially expensive non-const operation on an object, returning by const-value prevents you from accidentally calling this operation on a temporary. Imagine that +
returned a non-const value, and you could write:
(a + b).expensive();
In the age of C++11, however, it is strongly advised to return values as non-const so that you can take full advantage of rvalue references, which only make sense on non-constant rvalues.
In summary, there is a rationale for this practice, but it is essentially obsolete.
Why is returning a const from a function not being detected as a const?
y()
is a prvalue expression. The type of this expression is not const int
, but int
.
This is because the type of prvalue non-class non-array expressions have their cv-qualifiers stripped.
In other words, it will work if you use a class type, but not with non-class types.
This is just how the language works. There is no difference between a const int
and a int
prvalue and similar for other fundamental types. They are just values for which the qualifiers aren't useful.
In contrast the expression x
is a lvalue, not a prvalue, and therefore the cv-qualifier is not stripped. There is a difference between referring to an object via const
-qualified and non-const
-qualified lvalue.
But even then decltype
applied directly to an non-parenthesized name doesn't actually consider the type of the expression anyway, but instead considers the type of the declaration of the named entity. This is a special case. decltype((x))
would consider the expression type and yield const int&
, adding the lvalue reference because x
is a lvalue.
std::invoke_result
is also specified to return the decltype
of the INVOKE expression, so it will have the same issue.
You can obtain the const
-qualified type from the return type of the function type. A typical approach to that is partial specialization based on the function type. Unfortunately doing this properly is quite cumbersome since a lot of specializations must be written out to cover all cases. It also won't work if y
is overloaded or a generic lambda/functor.
My guess would be that e.g. boost::callable_traits::return_type
is implemented that way and will yield the const
-qualified int
.
It produces the expected result (see https://godbolt.org/z/7fYn4q9vs):
#include <type_traits>
#include <iostream>
#include <boost/callable_traits/return_type.hpp>
inline const int x = 1;
/// A constant integer variable.
inline const int y() {return x;}
/// A constant integer function returning <x>.
int main()
{
/// <x> successfully passes as being a constant.
std::cout << std::is_same_v <const int, decltype(x)> << " ";
/// And returning it from a function (<y>) now does as well.
std::cout << std::is_same_v <const int, boost::callable_traits::return_type_t<decltype(y)>> << std::endl;
}
Should I still return const objects in C++11?
Returning const objects is a workaround that might cause other problems. Since C++11, there is a better solution for the assignment issue: Reference Qualifiers for member functions. I try to explain it with some code:
int foo(); // function declaration
foo() = 42; // ERROR
The assignment in the second line results in a compile-time error for the builtin type int
in both C and C++. Same for other builtin types. That's because the assignment operator for builtin types requires a non-const lvalue-reference on the left hand side. To put it in code, the assignment operator might look as follows (invalid code):
int& operator=(int& lhs, const int& rhs);
It was always possible in C++ to restrict parameters to lvalue references. However, that wasn't possible until C++11 for the implicit first parameter of member functions (*this
).
That changed with C++11: Similar to const qualifiers for member functions, there are now reference qualifiers for member functions. The following code shows the usage on the copy and move operators (note the &
after the parameter list):
struct Int
{
Int(const Int& rhs) = default;
Int(Int&& rhs) noexcept = default;
~Int() noexcept = default;
auto operator=(const Int& rhs) & -> Int& = default;
auto operator=(Int&& rhs) & noexcept -> Int& = default;
};
With this class declaration, the assignment expression in the following code fragment is invalid, whereas assigning to a local variable works - as it was in the first example.
Int bar();
Int baz();
bar() = baz(); // ERROR: no viable overloaded '='
So there is no need to return const objects. You can restrict the assigment operators to lvalue references, so that everything else still works as expected - in particular move operations.
See also:
- What is "rvalue reference for *this"?
putting 'const' before declaring function in a class
const string get_name()
means that the return value is constant object. It prevents the caller of the function from calling non-const methods on the temporary return value, e.g.
x.get_name() = "bla"
x.get_name()[0] = 'a';
will not compile.
I think one of the main motivations was to prevent accidental assignments e.g. in if statements:
if (x.get_name() = "John Doe"))
It has been a guideline for some time, but C++11 made it obsolete by introducing rvalue references and move semantics.
In your example, ypu should probably return a const reference to name
. This will save you the creation of temporary object and keep the benefits of being const. And the method should be declared const
because it does not modify the object's state.
const& get_name() const { return name; };
const Class * const Function() What does the second const do?
const SimpleClass * const Myfunction()
{
return sc;
}
decltype(auto) p = Myfunction();
p = nullptr; // error due to the second const.
But the truth is that not many people uses decltype(auto), and your function will be normally called like:
const SimpleClass *p = Myfunction();
p = nullptr; // success, you are not required to specify the second const.
const auto* p = Myfunction();
p = nullptr; // success, again: we are not required to specify the second const.
And...
const SimpleClass * const p = Myfunction();
p = nullptr; // error
const auto* const p = Myfunction();
p = nullptr; // error
Returning a const pointer to a const data member and the 'auto' keyword. A bit confused
As already mentioned, auto
ignores top level cv-qualifiers. Read this article to learn the details of how auto
and decltype
work.
Now, even if auto
didn't ignore the const
, in your case, temp
would still not be const
because top level cv-qualifiers on return types are ignored if the type being returned is of non-class type.
g++ even produces the following warning with -Wextra
warning: type qualifiers ignored on function return type [-Wignored-qualifiers]
This can be demonstrated by using C++14's decltype(auto)
. Unlike auto
, decltype(auto)
doesn't discard references and top level cv-qualifiers. If you modify your example by adding the following lines the code will still compile, proving that temp
is not a const
pointer.
decltype(auto) temp = myClass.test();
static_assert(std::is_same<const int*, decltype(temp)>{}, "");
On the other hand, if test()
returns an object of class type with a top level cv-qualifier, then auto
would still discard const
, but decltype(auto)
wouldn't.
Live demo
In C++, is a const method returning a pointer to a non-const object considered bad practice?
It depends on whether the non-const pointer is pointing to something inside the const object, or whether it is newly allocated memory that is meant to be variable.
Consider something that returned a copy of a string; it's fine for the returned copy to be non-const, even if the original string is const. The ownership of the copy is being passed from the method to the calling code; ensuring that the copy is appropriately destructed is now the calling code's problem (quite possibly a very minor problem).
However, it would be a very bad idea to return a non-const pointer to something from inside a const object.
Related Topics
How to Check If the Input Is a Valid Integer Without Any Other Chars
Std::String Length() and Size() Member Functions
How to Get Total CPU Usage in Linux Using C++
How to Use Formatmessage() Properly in C++
Console Output in a Qt Gui App
Why Do I Need Double Layer of Indirection for MACros
Is There a Compiler Bug Exposed by My Implementation of an Is_Complete Type Trait
How to Use the Non-Default Constructor for a Member
The Precision of Std::To_String(Double)
How to Validate Input Using Scanf
How to Tell Where a Header File Is Included From
How to Determine the Size of an Object in C++
How to Make Visual Studio Use the Native Amd64 Toolchain
Which Std::Async Implementations Use Thread Pools
Why Does the Most Negative Int Value Cause an Error About Ambiguous Function Overloads