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