How is const
applied to a template member in a const
member function? I found the following to be interesting (this is in VS15):
class TcpSocket;
class TcpThread
{
TcpSocket* Listener() const;
std::vector< TcpSocket* > sockets_;
};
TcpSocket* TcpThread::Listener() const
{
auto s = sockets_.front();
return s;
}
I added the auto
to clarify what was going on. It is deduced as TcpSocket*
, so the non-const version of front
is being selected. However, if I insert
sockets_.erase(sockets_.begin());
as the first line of code, it fails to compile, essentially saying that sockets_
is const
.
It makes sense for it to work as it does, but there is evidently more going on here than simply "treat each member as const
in a const
member function.
Member functions can be function templates in several contexts. All functions of class templates are generic but aren't referred to as member templates or member function templates. If these member functions take their own template arguments, they're considered to be member function templates.
const member functions Declaring a member function with the const keyword specifies that the function is a "read-only" function that doesn't modify the object for which it's called. A constant member function can't modify any non-static data members or call any member functions that aren't constant.
When the name of a member template specialization appears after . or -> in a postfix-expression, or after nested-name-specifier in a qualified-id, and the postfix-expression or qualified-id explicitly depends on a template-parameter (14.6. 2), the member template name must be prefixed by the keyword template .
A non-template class can have template member functions, if required. Notice the syntax. Unlike a member function for a template class, a template member function is just like a free template function but scoped to its containing class.
sockets_
inside Listener
is const
. Let's have a look at what front
returns:
reference front();
const_reference front() const;
So we'll get a const_reference
, in this case a TcpSocket * const&
.
This is where your expectation is incorrect. Stripping away the reference for sake of clarity, you expect a const TcpSocket*
, it gives you a TcpSocket * const
. The former is a pointer to a const TcpSocket
, the latter is a const
pointer to a TcpSocket
.
So what front
gives you is a pointer which you can't change to a TcpSocket
which you can change.
As such, it's perfectly valid to make a non-const copy of this pointer with its pointee available for modification:
auto s = sockets_.front();
//sockets_.front() returns TcpSocket* const
//s copies it to a TcpSocket*
It's not that the non-const version of front
is called, it's just that you're storing pointers, and then you're putting it into auto
which always deduces by-value (and not by reference--for which you need auto& =
). Because you're copying the const pointer, you then have your own copy, and so const
is omitted for it, unless you explicitly define it that way. That's why you're deducing TcpSocket*
instead of TcpSocket* const
.
If you want to verify this, try doing auto& s = _sockets.front()
and see what type you get then.
Note. too, that since you're storing a pointer, that vector::const_reference
you'd get back would be point to a const pointer and not a pointer to const.
The container itself, being const in that scope, means that you can't change its sequence of elements, or what they point to. So you can't say _sockets.erase()
nor can you say _sockets[0]
. However, since the elements themselves are pointers to a non-const TcpSocket
, that means that you can do pretty much whatever you want with them. It's the container you can't fiddle with.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With