Reputation: 128
In my time as a C++ programmer, I've typically thought that name-hiding should be avoided because it can lead to confusion. For example, if you have a base class with a function f() and a derived class with a function f(), and f() is not virtual, a caller might accidentally call the wrong f().
However, I ran into an example while reading Herb Sutter's Exceptional C++ book where a case of name hiding seems to be encouraged. To make a case-insensitive string, he suggests that we make a case-insensitive version of std::basic_string<>
by creating a char_traits
class that inherits from char_traits<char>
(which is a non-polymorphic static class) like so:
struct ci_char_traits : public char_traits<char>
{
static bool eq( char c1, char c2 ) { /*...*/ }
static bool lt( char c1, char c2 ) { /*...*/ }
static int compare( const char* s1,
const char* s2,
size_t n) { /*...*/ }
static const char*
find( const char* s, int n, char a ) { /*...*/ }
}
(I left out the function definitions to make it more concise). Essentially, we are inheriting all of the functions of char_traits<char>
, and we are hiding the ones that we want to modify in order to make it case-insensitive. Then, you define the case insensitive string like so:
typedef basic_string<char, ci_char_traits> ci_string;
Now I do think this is a pretty neat trick! But I wonder if this kind of thing is considered 'good practice' because name-hiding is typically frowned upon. Perhaps it is more acceptable to name-hide static member functions than other types of functions?
Upvotes: 2
Views: 522
Reputation: 69892
The difference here is that the traits class is not polymorphic, so there will never be any confusion as to which interface is used.
ci_char_traits
could of course be implemented with private inheritance, encapsulation or simply deferring to std::char_traits<>
. All of these approaches would require more maintenance.
There has been some discussion in the comments on whether templates are polymorphic. I will elaborate.
The construct std::basic_string<char, ci_char_traits>
causes template expansion of the template std::basic_string<class CharT, class Traits, class Allocator>
. During this expansion, the following type substitutions are made:
CharT
is replaced with char
Traits
is replaced with ci_char_traits
Allocator
is replaced with std::allocator<char>
Therefore, the class generated by the compiler is:
std::basic_string<char, ci_char_traits, std::allocator<char>>
This is not a polymorphic class.
This class will have a member typedef called std::basic_string<char, ci_char_traits, std::allocator<char>>::traits_type
, which will unequivocally be the type ci_char_traits
.
Therefore, this template expansion will result in a non-polymorphic class which uses the non-polymorphic class ci_char_traits
to determine the behaviour of character-based operations such as comparison.
Which overload of eq
and lt
will be called by code in class std::basic_string<char, ci_char_traits, std::allocator<char>>
is precisely defined, which is why in this case, it is not bad practice to overload these static member functions.
Upvotes: 2
Reputation: 15162
In the case-insensitive example it is more a matter of implementing an interface, it just happens that because this interface is done via templates that it is a compile-time matter rather than run-time.
To implement std::char_traits you simply cannot use different names (at least without a great deal of mucking with basic_string instead), those names are part of the interface.
Upvotes: 0