Potentially related articles:
Overload resolution between object, rvalue reference, const reference
std::begin and R-values
For a STL container C, std::begin(C) and similar access functions including std::data(C) (since C++17) are supposed to have the same behavior of C::begin() and the other corresponding C's methods. However, I am observing some interesting behaviors due to the details of overload resolution involving lvalue/rvalue references and constness.
DataType1 is int* as easily expected. Also, confirmed the by with Boost's type_id_with_cvr. const vector<int> gives int const* No surprise here.
using T = vector<int>;
using DataType1 = decltype(T().data()); // int*
using CT = const T;
using DataType2 = decltype(CT().data()); // int const*
using boost::typeindex::type_id_with_cvr;
cout << type_id_with_cvr<DataType1>() << endl; // prints int*
...
I tried std::data, which can also handle arrays and non-STL containers. But it yields int const*. Similarly, std::begin returns a const iterator, even though T is not const.
using T = vector<int>;
using DataType3 = decltype(std::data(T())); // int const* Why ???
using CT = const T;
using DataType4 = decltype(std::data(CT())); // int const*
Question: What is the reason of this difference? I expected that C.data() and std::data(C) would behave in the same manner.
Some my research: In order to get int* for DataType3, T must be converted to non-const lvalue reference type explicitly. I tried declval, and it was working.
using DataType3 = decltype(std::data(std::declval<T&>())); // int*
std::data provides two overloads:
template <class _Cont> constexpr
auto data(_Cont& __c) -> decltype(__c.data()) { return __c.data(); }
// non-const rvalue reference argument matches to this version.
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
While resolving overloaded functions for std::data, T(), which is non-const rvalue reference, is matched to the const T& version instead of T& version.
It was not easy to find this specific overload resolution rule in the standard (13.3, over.match). It'd be much clearer if someone could point the exact rules for this issue.
This behaviour is attributed to overload resolution rules. As per standard 8.5.3/p5.2 References [dcl.init.ref], rvalue references bind to const lvalue references. In this example:
std::data(T())
You provide to std::data an rvalue. Thus, due to overload resolution rules the overload:
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
is a better match. Consequently you get const int*
You can't bind a temporary to a non-const lvalue reference.
The only line that is mildly surprising is using DataType1 = decltype(T().data()); // int*.
...but it is still normal, member functions can be called on temporary objects without being treated as const. This is another non trivial difference between member functions and free functions.
For example, in C++98 (pre-rvalue refs) it was not possible to do std::ofstream("file.txt") << std::string("text") because operator<< is not member and the temporary is treated as const. If operator<< were a member of std::ofstream it would be possible (and may even make sense). (The situation changed later in C++11 with rvalue references but the point is still valid).
Here it is an example:
#include<iostream>
struct A{
void f() const{std::cout << "const member" << std::endl;}
void f(){std::cout << "non const member" << std::endl;}
};
void f(A const&){std::cout << "const function" << std::endl;}
void f(A&){std::cout << "non const function" << std::endl;}
int main(){
A().f(); // prints "non const member"
f(A()); // prints "const function"
}
The behavior is exposed when the object is temporarily constructed and used. In all other cases I can imagine, member f is equivalent to f free function.
(r-value reference qualifications && --for member and function-- can give you a more fine grained control but it was not part of the question.)
Related
I've been digging around ref-qualifiers a bit, following on a previous question.
Given the code sample below;
#include <iostream>
#include <string>
#include <utility>
struct A {
std::string abc = "abc";
std::string& get() & {
std::cout << "get() &" << std::endl;
return abc;
}
std::string get() && {
std::cout << "get() &&" << std::endl;
return std::move(abc);
}
std::string const& get() const & {
std::cout << "get() const &" << std::endl;
return abc;
}
std::string get() const && {
std::cout << "get() const &&" << std::endl;
return abc;
}
};
int main()
{
A a1;
a1.get();
const A a2{};
a2.get();
A().get();
const A a3{};
std::move(a3).get();
}
And the output is as you would expect:
get() &
get() const &
get() &&
get() const &&
This compiles and runs with clang and gcc 4.9.1 (not 4.9.0 though). Live sample here.
In general code (the sample is there to see how the code compiles and runs).
What would the purpose of the const && ref-qualifier on a method be?
The method is unable to modify the contents on the object (it is const), an attempt to return std::move(abc); from the const && method doesn't actually move the std::string at all. Presumably you would want to be able modify the object, since it's an r-value and won't be around for long. If the const && qualified method were to be removed, the code std::move(a3).method() would bind to the const & qualified method, which would make sense.
What, if any, would the implied semantic difference be between a method qualified as const & and one qualified as const &&? I.e. how would the implementation vary or why would you want both?
Would the std::string truely be able to be "moved" out of the temporary object?
What would a "canonical" signature look like for std::string get() const && in this case?
On the usefulness of const&&... (in general)
The usefulness of the const&& qualifier on the member method is minimal at best. The object cannot be modified in the same manner as a && method would allow it to be modified; it is const after all (as noted, mutable does change this). So we will not be able to rip out its guts, since the temporary is expiring anyway, as we would in something akin to a normal move.
In many ways the usefulness of the const&& may be best evaluated in the context of how useful an object of type const T&& is to begin with. How useful is a const T&& function argument? As pointed out in another answer (to this question) here, they are very useful in declaring functions deleted, e.g. in this case
template <class T> void ref (const T&&) = delete;
to explicitly disallow objects of prvalue and xvalue value category types from being used with the functions, and const T&& does bind to all prvalue and xvalue objects.
What is the usefulness of const&& method qualifier?
It is interesting to note that in the proposal C++ library extensions, optional, § 5.3, includes overloads, e.g.
constexpr T value() const &&;
that are qualified as const&& and are specified to perform the same action as the && alternative.
The reason I can infer for this case; is that this is for completeness and correctness. If the value() method is called on an rvalue, then it performs the same action independent of it being const or not. The const will need to be dealt with by the contained object being moved or the client code using it. If there is some mutable state with the object being contained, then that state can legitimately be changed.
There may well still be some merit in this; in no particular order...
To declare it = delete to prohibit the method's use on prvalues and xvalues.
If the type has mutable state and the qualifier makes sense (possibly in addition to the other qualifiers) in the target environment, consider it.
If you are implementing a generic container type, then for completeness and correctness, consider adding it and performing the same action as the && method. Advice here is sort from the standard library (and its extensions).
What would a "canonical" signature look like for a const&& qualified method?
Since the method will be performing the same action as the && method, I would advocate that the signature matches the && signature.
We can find a similar exploration of this issue in the article What are const rvalue references good for? and the one use that stood out is this example form the standard library:
template <class T> void ref (const T&&) = delete;
template <class T> void cref (const T&&) = delete;
which disables ref and cref for rvalues altogether. We can find these declarations in the draft C++11 standard section 20.8 Function objects paragraph 2.
Scott Meyers alludes to this use in Universal References in C++11:
Even the simple addition of a const qualifier is enough to disable the
interpretation of “&&” as a universal reference:
Suppose we have a type with a mutable state. Then const&& will both allow us to mutate that state, and indicate that such mutation is safe.
struct bar;
struct foo {
mutable std::vector<char> state;
operator bar() const&;
operator bar() const&&;
};
const is not absolute.
Barring mutable state, it is not safe to cast away const in a const&& method in order to extract state, because extracting state in this way from an actual const object is undefined behavior.
I see two main uses for ref-qualifying a method. One is like you show in your get() && method, where you use it to select a potentially more efficient implementation that is only available when you know the object will no longer be used. But the other is a safety hint to prevent calling certain methods on temporary objects.
You can use notation like get() const && = delete in such cases, although realistically I would save this approach for modifying methods, especially those that are potentially costly. It doesn't make much sense to mutate and then discard an object without retrieving something, and doubly so if it's expensive to perform the mutation. This construct gives the compiler a way to flag and prevent such usage.
std::is_function is specialized for types which have signature similar to:
int(int) &
see here:std::is_function
But this is neither a pointer to a member method, which signature could be:
int(T::*)(int) &
Nor it can be a reference to a function:
int (&)(int)
So what is this strange signature?
It's a function type which only exists in the type system. It cannot ever be created.
But this is neither a pointer to a member method, which signature could be:
int(T::*)(int) &
It's this, without the pointer. The type system allows you to describe that as a type.
#include <type_traits>
struct T { };
using A = int(int) &;
using B = A T::*;
using C = int(T::*)(int) &;
static_assert(std::is_same_v<B, C>);
#T.C. mentions PR0172R0, which discusses how the presence of these types causes issues for library writers, and suggests several options which might reduce those issues. One of the options is getting rid of them entirely, others reduce their impact. Depending on how this goes, this answer may or may not be correct for future versions of C++.
On the documentation page you link to, you'll see this comment:
// specialization for function types that have ref-qualifiers
above the list the examples you reference come from.
Those are functions with ref-qualifiers, which you can read more about here.
In short, they are similar to const qualified functions. Here's an example:
struct foo
{
void bar() & { std::cout << "this is an lvalue instance of foo" << "\n"; }
void bar() && { std::cout << "this is an rvalue instance of foo" << "\n"; }
};
int main(int argc, char* argv[])
{
foo f{};
f.bar(); // prints "this is an lvalue instance of foo"
std::move(f).bar(); // prints "this is an rvalue instance of foo"
return 0;
}
I can't think of a great use case for this feature, but it is possible to use.
Since the beginning of times (referring to the first C++ standard) you could declare such "strange" function types as, for example
typedef int F() const;
Despite the fact that the above declaration does not immediately involve any classes, the trailing const in this case can only serve as const-qualification of a non-static class member function. This restricts the usage of the above typedef-name to class member declarations. For example, one could use it as follows
struct S {
F foo; // Declares an `int S::foo() const` member function
};
int S::foo() const { // Defines it
return 42;
}
F S::*p = &S::foo; // Declares 'p' as `int (S::*)() const` pointer
Note that, however obscure, this is a "classic" C++ feature that's been in the language for a long time.
What you have in your example is effectively the same thing, but with C++11 ref-qualifier in place of const qualifier.
#include <type_traits>
struct foo;
int main()
{
const foo *bar;
static_assert(std::is_const<decltype(*bar)>::value,
"expected const but this is non-const!");
}
This results in a failing static_assert which is unexpected. This is somewhat similar to this question on const references but not quite the same.
In my case, dereferencing bar should give an instance of const foo as its type but yet std::is_const is saying otherwise.
Shortly that's because a reference or a pointer to a const type is not a const type.
Note that decltype(*bar) isn't const foo, it's const foo & and they are really different beasts.
Consider the example given here:
std::cout << std::is_const<const int *>::value << '\n'; // false
std::cout << std::is_const<int * const>::value << '\n'; // true
We see that std::is_const<const int *>::value is false and std::is_const<int * const>::value is true.
That's because in const int * the type is pointer to something const, that is not a const type as intended by is_const (and the standard actually). In int * const the const qualifier applies to the pointer type and not to the pointed one, thus the type is a const one, no matter to what it points.
Something similar applies for const foo &, that is a reference to something const.
You can solve using this instead:
static_assert(std::is_const<std::remove_reference_t<decltype(*bar)>>::value, "expected const but this is non-const!");
Or even this, for you don't need to do *bar actually:
static_assert(std::is_const<std::remove_pointer_t<decltype(bar)>>::value, "expected const but this is non-const!");
In this case, by removing the pointer/reference with remove_pointer_t/remove_reference_t your type becomes const foo, that is actually a const type.
As a side note, the example above uses the C++14-ish std::remove_reference_t and std::remove_pointer_t type traits.
You can easily turn those lines of code to C++11 as it follows:
static_assert(std::is_const<typename std::remove_pointer<decltype(bar)>:: type>::value, "expected const but this is non-const!");
It's worth mentioning a few comments to the answer to give more details:
Thanks to #DanielFischer for the question:
Is there a short explanation why decltype(*bar) is const foo& rather than const foo?
I'm not a language-lawyer, but I guess it can be deduced from [expr.unary.op]/1 (emphasis mine):
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.
And [dcl.type.simple]/4.4 (emphasis mine):
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
Both referring to the working draft.
Thanks to #LightnessRacesInOrbit for the comment. Note that decltype(*bar) being const foo & is a funny C++ quirk of decltype, since *bar is not const foo &.
I'm working on g++ and here I have tried to overload a function by just adding const to parameter. It works fine and when it runs, it calls the function without const
Is this behavior specified in the C++ standard?
What the reason it calls the function without const
void print(const std::string& str){std::cout << "const" << str << std::endl;}
void print(std::string& str){std::cout << str << std::endl;}
int main()
{
std::string temp = "hello";
print(temp);
return 0;
}
Reference bindings are an identity category §13.3.3.1.4) but since the latter is more cv-qualified, for §13.3.3.2, the non-const is preferred (sample code from the standard):
int f(const int &);
int f(int &);
int i;
int j = f(i); // calls f(int &)
That is standard behavior. Any other behavior would lead to crazy behavior. In particular, the non-const function would not be callable at all.
const is part of method signature. Overriding works only for methods with the same signature.
This behavior was made to avoid reverse situation when you use const method of base class to call not const method of child class.
The reason is this section in [over.ics.rank]/3 where this case is explicitly covered:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if […] — S1 and S2 are reference
bindings (8.5.3), and the types to which the references refer are the
same type except for top-level cv-qualifiers, and the type to which
the reference initialized by S2 refers is more cv-qualified than the
type to which the reference initialized by S1 refers.
S1 corresponds to the second overload and S2 to the first.
What the reason it calls the function without const
You always try to select the most specialized thing. That is the case in overload resolution just as it is in partial ordering of function templates. The second overload is more specialized than the first because the first can be called with arguments which the second cannot be called with - that is the basic reasoning behind this rule.
Overloading works by matching the types of the arguments, including the qualifiers. In your case temp has type std::string not const std::string. You have only initialised it with a literal constant, it is not itself constant.
Consider the following:
std::string temp( "hello" ) ;
print(temp); // hello
print( std::string("hello") ) ; // consthello
print( "hello" ) ; // consthello
print( static_cast<const std::string>(temp) ) ; // consthello
const std::string temp2( "hello" ) ;
print(temp2); // consthello
If you were to remove the non-const version, all three will call the remaining const overload. In this example, only the const version is in fact necessary (and preferred) since neither version modify the string object.
If on the other hand you removed the non-const version, there would be no function matching any but the first example above, and the build would fail. That is to say a non-const object can safely be passed as a const argument, but a const object cannot be passed as a non-const argument, because the function is not "promising" not to modify the object. You can force a const into a non-const argument by a const_cast as in:
const std::string temp2("hello") ;
print( const_cast<std::string&>(temp2) ) ; // hello
But if print() were to attempt to modify the object in this case the results are undefined, so consider the practice unsafe.
Making an argument const indicates intent, allows the compiler to issue a diagnostic if the code is attempts to modify the object or pass it via a non-const argument to some other function. It may also potentially provide the compiler with optimisation possibilities.
Because calling the function taking std::string const& requires two implicit conversions: one to std::string const, one to std::string const&; whereas calling the function taking std::string& requires merely one implicit conversion (to std::string&), so that one is preferred.
Consider the following code snippet:
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
const int& get() const {return x;}
};
void print (const MyClass& arg) {
cout << arg.get() << '\n';
}
int main() {
MyClass foo (10);
print(foo);
return 0;
}
Whether I add a const modifier before the instatiatation of MyClass or not, the program successfully compiles (without any warning) and prints 10. Why can print accept a non-const argument? Or, in other words, what is the function of the const modifier in the function parameter? Why can the formal and actual parameters of a function have different types (or modifiers)?
I have tried both GCC (4.8.2) and Clang (3.4) with -Wall -std=c++11 on Ubuntu 14.04, and the results were the same (no errors/warnings). I have also searched "c++ const object function" but didn't get anything that looked promising.
This is completely sane and normal. The object is treated as const within your function; it does not matter that it was not originally created to be immutable.
Of course, the opposite is not true!
void foo(T& rarr);
int main()
{
const T lolwut;
foo(lolwut); // oops
}
const forbids the body of the function from modifying the parameter variable. Both ways compile because you didn't attempt to modify it.
You can overload const and non-const reference parameters, and the const overload will only be chosen if the argument is really const (or a type conversion results in a temporary being passed). (For non-reference parameters, const seldom makes sense and such overloads may not even be defined.)
All that const does in this case is to prevent modification of parameter variable (and in the case of classes, prevent the calling of functions that are not labelled as const). MyClass may be trivially cast to const MyClass because there should be nothing that you can do to a const MyClass that you can't do to a non-const one. The reverse is not true, of course.
(I say "should" above, because it is of course possible to completely subvert const semantics under C++ if you wanted to, so the presence of const in a function prototype is really only a hopeful hint rather than a cast-iron compiler-enforced guarantee. But no sensible programmer should be breaking things like that!)