Patrick Avery
Patrick Avery

Reputation: 128

Name-hiding static member functions in C++: good practice?

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

Answers (2)

Richard Hodges
Richard Hodges

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.

Update:

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

SoronelHaetir
SoronelHaetir

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

Related Questions