EXPLORATION 52

image

Names and Namespaces

Nearly every name in the standard library begins with std::, and only names in the standard library are permitted to start with std::. For your own names, you can define other prefixes, which is a good idea and an excellent way to avoid name collisions. Libraries and large programs in particular benefit from proper partitioning and naming. However, templates and names have some complications, and this Exploration helps clarify the issues.

Namespaces

The name std is an example of a namespace, which is a C++ term for a named scope. A namespace is a way to keep names organized. When you see a name that begins with std::, you know it’s in the standard library. Good third-party libraries use namespaces. The open-source Boost project (www.boost.org), for example, uses the boost namespace to ensure names, such as boost::container::vector, do not interfere with similar names in the standard library, such as std::vector. Applications can take advantage of namespaces too. For example, different project teams can place their own names in different namespaces, so members of one team are free to name functions and classes without the need to check with other teams. For example, the GUI team might use the namespace gui and define a gui::tree class, which manages a tree widget in a user interface. The database team might use the db namespace. Thus, db::tree might represent a tree data structure that is used to store database indexes on disk. A database debugging tool can use both tree classes, because there is no clash between db::tree and gui::tree. The namespaces keep the names separate.

To create a namespace and declare names within it, you must define the namespace. A namespace definition begins with the namespace keyword, followed by an optional identifier that names the namespace. This, in turn, is followed by declarations within curly braces. Unlike a class definition, a namespace definition does not end with a semicolon after the closing curly brace. All the declarations within the curly braces are in the scope of the namespace. You must define a namespace outside of any function. Listing 52-1 defines the namespace numeric and, within it, the rational class template.

Listing 52-1.  Defining the rational Class Template in the numeric Namespace

#ifndef RATIONAL_HPP_
#define RATIONAL_HPP_
 
namespace numeric
{
  template<class T>
  class rational
  {
    ... you know what goes here...
  };
  template<class T>
  bool operator==(rational<T> const& a, rational<T> const& b);
  template<class T>
  rational<T> operator+(rational<T> const& a, rational<T> const& b);
  ... and so on...
} // namespace numeric
 
#endif

Namespace definitions can be discontiguous. This means that you can have many separate namespace blocks that all contribute to the same namespace. Therefore, multiple header files can each define the same namespace, and every definition adds names to the same, common namespace. Listing 52-2 illustrates how to define the fixed class template within the same numeric namespace, even in a different header (say, fixed.hpp).

Listing 52-2.  Defining the fixed Class Template in the numeric Namespace

#ifndef FIXED_HPP_
#define FIXED_HPP_
 
namespace numeric
{
  template<class T, int N>
  class fixed
  {
    ... copied from Exploration 51...
  };
  template<class T, int N>
  bool operator==(fixed<T,N> const& a, fixed<T,N> const& b);
  template<class T, int N>
  fixed<T,N> operator+(fixed<T,N> const& a, fixed<T,N> const& b);
  // and so on...
} // namespace numeric
 
#endif

Note how the free functions and operators that are associated with the class templates are defined in the same namespace. I’ll explain exactly why later in the Exploration, but I wanted to point it out now, because it’s very important.

When you declare but don’t define an entity (such as a function) in a namespace, you have a choice for how to define that entity, as described in the following:

  • Use the same or another namespace definition and define the entity within the namespace definition.
  • Define the entity outside of the namespace and prefix the entity name with the namespace name and the scope operator (::).

Listing 52-3 illustrates both styles of definition. (The declarations are in Listings 52-1 and 52-2.)

Listing 52-3.  Defining Entities in a Namespace

namespace numeric
{
  template<class T>
  rational<T> operator+(rational<T> const& a, rational<T> const& b)
  {
    rational<T> result{a};
    result += b;
    return result;
  }
}
 
template<class T, int N>
numeric::fixed<T, N> numeric::operator+(fixed<T, N> const& a, fixed<T, N> const& b)
{
  fixed<T, N> result{a};
  result += b;
  return result;
}

The first form is straightforward. As always, the definition must follow the declaration. In a header file, you might define an inline function or function template using this syntax.

In the second form, the compiler sees the namespace name (numeric), followed by the scope operator, and knows to look up the subsequent name (operator*) in that namespace. The compiler considers the rest of the function to be in the namespace scope, so you don’t have to specify the namespace name in the remainder of the declaration (that is, the function parameters and the function body). The function’s return type comes before the function name, which places it outside the namespace scope, so you still have to use the namespace name. To avoid ambiguity, you are not allowed to have a namespace and a class with the same name in a single namespace.

The alternative style of writing a function return type that I touched on in Exploration 23 lets you write the return type without repeating the namespace scope, because the function name establishes the scope for you, as shown in Listing 52-4.

Listing 52-4.  Alternative Style of Function Declaration in a Namespace

template<class T, int N>
auto numeric::operator+(fixed<T, N> const& a, fixed<T, N> const& b) -> fixed<T, N>
{
  fixed<T, N> result{a};
  result += b;
  return result;
}

Traditionally, when you define a namespace in a header, the header contains a single namespace definition, which contains all the necessary declarations and definitions. When you implement functions and other entities in a separate source file, I find it most convenient to write an explicit namespace and define the functions inside the namespace, but some programmers prefer to omit the namespace definition. Instead, they use the namespace name and scope operator when defining the entities. An entity name that begins with the namespace name and scope operator is an example of a qualified name—that is, a name that explicitly tells the compiler where to look to find the name’s declaration.

The name rational<int>::value_type is qualified, because the compiler knows to look up value_type in the class template rational, specialized for int. The name std::vector is a qualified name, because the compiler looks up vector in the namespace std. On the other hand, where does the compiler look up the name std? Before I can answer that question, I have to delve into the general subject of nested namespaces.

Nested Namespaces

Namespaces can nest, that is, you can define a namespace inside another namespace, as demonstrated in the following:

namespace exploring_cpp
{
  namespace numeric {
    template<class T> class rational
    {
      // and so on ...
    };
  }
}

To use a nested namespace, the qualifier lists all the namespaces in order, starting from the outermost namespace. Separate each namespace with the scope operator (::).

exploring_cpp::numeric::rational<int> half{1, 2};

A top-level namespace, such as std or exploring_cpp, is actually a nested namespace. Its outer namespace is called the global namespace. All entities that you declare outside of any function are in a namespace—in an explicit namespace or in the global namespace. Thus, names outside of functions are said to be at namespace scope. The phrase global scope refers to names that are declared in the implicit global namespace, which means outside of any explicit namespace. Qualify a global name by prefixing the name with a scope operator.

::exploring_cpp::numeric::rational<int> half{1, 2};

Most programs you read will not use an explicit global scope operator. Instead, programmers tend to rely on the normal C++ rules for looking up names, letting the compiler find global names on its own. So far, every function you’ve written has been global; every call to these functions has been unqualified. The compiler has never had a problem with the unqualified names. If you have a situation in which a local name hides a global name, you can refer to the global name explicitly. Listing 52-5 demonstrates the kind of trouble you can wreak through poor choice of names and how to use qualified names to extricate yourself.

Listing 52-5.  Coping with Conflicting Names

 1 #include <cmath>
 2 #include <numeric>
 3 #include <vector>
 4
 5 namespace stats {
 6   // Really bad name for a functor to compute sum of squares,
 7   // for use in determining standard deviation.
 8   class std
 9   {
10   public:
11     std(double mean) : mean_{mean} {}
12     double operator()(double acc, double x)
13     const
14     {
15       return acc + square(x - mean_);
16     }
17     double square(double x) const { return x * x; }
18   private:
19     double mean_;
20   };
21
22   // Really bad name for a function in the stats namespace.
23   // It computes standard deviation.
24   double stats(::std::vector<double> const& data)
25   {
26     double std{0.0}; // Really, really bad name for a local variable
27     if (not data.empty())
28     {
29       double sum{::std::accumulate(data.begin(), data.end(), 0.0)};
30       double mean{sum / data.size()};
31       double sumsq{::std::accumulate(data.begin(), data.end(), 0.0,
32                    stats::std(mean))};
33       double variance{sumsq / data.size() - mean * mean};
34       std = ::std::sqrt(variance);
35     }
36     return std;
37   }
38 }

The local variable std does not conflict with the namespace of the same name because the compiler knows that only class and namespace names can appear on the left-hand side of a scope operator. On the other hand, the class std does conflict, so the use of a bare std:: qualifier is ambiguous. You must use ::std (for the standard library namespace) or stats::std (for the class). References to the local variable must use a plain std.

The name stats on line 24 names a function, so it does not conflict with the namespace stats. Therefore, the use of stats::std on line 32 is not ambiguous.

The accumulate algorithm, called on lines 29 and 31, does exactly what its name suggests. It adds all the elements in a range to a starting value, either by invoking the + operator or by calling a binary functor that takes the sum and a value from the range as arguments.

Remove the global scope operators from ::std::accumulate (lines 29 and 31) to give std::accumulate. Recompile the program. What messages does your compiler give you?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Restore the file to its original form. Remove the first :: qualifier from ::std::vector (line 24). What message does the compiler give you?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Restore the file to its original form. Remove the stats:: qualifier from stats::std (line 32). What message does the compiler give you?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Sane and rational people do not deliberately name a class std in a C++ program, but we all make mistakes. (Maybe you have a class that represents a building element in an architectural CAD system, and you accidentally omitted the letter u from stud.) By seeing the kinds of messages that the compiler issues when it runs into name conflicts, you can better recognize these errors when you accidentally create a name that conflicts with a name invented by a third-party library or another team working on your project.

Most application programmers don’t have to use the global scope prefix, because you can be careful about choosing names that don’t conflict. Library authors, on the other hand, never know where their code will be used or what names that code will use. Therefore, cautious library authors often use the global scope prefix.

Global Namespace

Names that you declare outside of all namespaces are global. In the past, I used global to mean “outside of any function,” but that was before you knew about namespaces. C++ programmers refer to names declared at namespace scope, which is our way of saying, “outside of any function.” Such a name can be declared in a namespace or outside of any explicit namespace.

A program’s main function must be global, that is, at namespace scope, but not in any namespace. If you define another function named main in a namespace, it does not interfere with the global main, but it will confuse anyone who reads your program.

The std Namespace

As you know, the standard library uses the std namespace. You are not allowed to define any names in the std namespace, but you can specialize templates that are defined in std, provided at least one template argument is a user-defined type.

The C++ standard library inherits some functions, types, and objects from the C standard library. You can recognize the C-derived headers, because their names begin with an extra letter c; e.g., <cmath> is the C++ equivalent of the C header <math.h>. Some C names, such as EOF, do not follow namespace rules. These names are usually written in all capital letters, to warn you that they are special. You don’t have to concern yourself with the details; just be aware that you cannot use the scope operator with these names, and the names are always global. When you look up a name in a language reference, these special names are called macros. (You’ve already seen one example: assert, declared in the <cassert> header, even though it is not all capital letters.)

The C++ standard grants some flexibility in how a library implementation inherits the C standard library. The specific rule is that a C header of the form <header.h> (for some C headers, such as math) declares its names in the global namespace, and the implementation decides whether the names are also in the std namespace. The C header with the form <cheader> declares its names in the std namespace, and the implementation may also declare them in the global namespace. Regardless of the style you choose, all C standard functions are reserved to the implementation, which means you are not free to use any C standard function name in the global namespace. If you want to use the same name, you must declare it in a different namespace. Some people like <cstddef> and std::size_t, and others prefer <stddef.h> and size_t. Pick a style and stick with it.

My recommendation is not to get caught up in which names originate in the C standard library and which are unique to C++. Instead, consider any name in the standard library off-limits. The only exception is when you want to use the same name for the same purpose, but in your own namespace. For example, you may want to overload the abs function to work with rational or fixed objects. Do so in their respective namespaces, alongside all the overloaded operators and other free functions.

image Caution  Many C++ references omit the C portions of the standard library. As you can see, however, the C portions are most problematic when it comes to name clashes. Thus, make sure your C++ 11 reference is complete or supplement your incomplete C++ reference with a complete C 99 library reference.

Using Namespaces

In order to use any name, the C++ compiler must be able to find it, which means identifying the scope in which it is declared. The most direct way to use a name from a namespace, such as rational or fixed, is to use a qualified name—that is, the namespace name as a prefix, e.g., numeric, followed by the scope operator(::).

numeric::rational<long> pi_r{80143857L, 25510582L};
numeric::fixed<long, 6> pi_f{3, 141593};

When the compiler sees the namespace name and the double colons (::), it knows to look up the subsequent name in that namespace. There is no chance of a collision with the same entity name in a different namespace.

Sometimes, however, you end up using the namespace a lot, and brevity becomes a virtue. The next two sections describe a couple of options.

The using Directive

You’ve seen a using directive before, but in case you need a refresher, take a look at the following:

using namespace std;

The syntax is as follows: the using keyword, the namespace keyword, and a namespace name. A using directive instructs the compiler to treat all the names in the namespace as though they were global. (The precise rule is slightly more complicated. However, unless you have a nested hierarchy of namespaces, the simplification is accurate.) You can list multiple using directives, but you run the risk of introducing name collisions among the namespaces. A using directive affects only the scope in which you place it. Because it can have a big impact on name lookup, restrict using directives to the narrowest scope you can; typically this would be an inner block.

Although a using directive has its advantages—and I use them in this book—you must be careful. They hinder the key advantage of namespaces: avoidance of name collisions. Names in different namespaces don’t ordinarily collide, but if you try to mix namespaces that declare a common name, the compiler will complain.

If you are careless with using directives, you can accidentally use a name from the wrong namespace. If you’re lucky, the compiler will tell you about your mistake, because your code uses the wrong name in a way that violates language rules. If you aren’t lucky, the wrong name will coincidentally have the same syntax, and you won’t notice your mistake until much, much later.

Never place a using directive in a header. That ruins namespaces for everyone who includes your header. Keep using directives as local as possible, in the smallest scope possible.

In general, I try to avoid using directives. You should get used to reading fully qualified names. On the other hand, sometimes long names interfere with easy comprehension of complicated code. Rarely do I use more than one using directive in the same scope. So far, the only times I’ve ever done so is when all the namespaces are defined by the same library, so I know they work together, and I won’t run into naming problems. Listing 52-6 illustrates how using directives work.

Listing 52-6.  Examples of using Directives

 1 #include <iostream>
 2
 3 void print(int i)
 4 {
 5   std::cout << "int: " << i << ' ';
 6 }
 7
 8 namespace labeled
 9 {
10   void print(double d)
11   {
12     std::cout << "double: " << d << ' ';
13   }
14 }
15
16 namespace simple
17 {
18   void print(int i)
19   {
20     std::cout << i << ' ';
21   }
22   void print(double d)
23   {
24     std::cout << d << ' ';
25   }
26 }
27
28 void test_simple()
29 {
30   using namespace simple;
31   print(42);                // ???
32   print(3.14159);           // finds simple::print(double)
33 }
34
35 void test_labeled()
36 {
37   using namespace labeled;
38   print(42);                // find ::print(int)
39   print(3.14159);           // finds labeled::print(double)
40 }
41
42 int main()
43 {
44   test_simple();
45   test_labeled();
46 }

What will happen if you try to compile Listing 52-5?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

The error is on line 31. The using directive effectively merges the simple namespace with the global namespace. Thus, you now have two functions named print that take a single int argument, and the compiler doesn’t know which one you want. Fix the problem by qualifying the call to print(42) (on line 32), so it calls the function in the simple namespace. What do you expect as the program output?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Try it. Make sure you get what you expect. Line 31 should now look like the following:

simple::print(42);

The using Declaration

More specific, and therefore less dangerous, than a using directive is a using declaration. A using declaration imports a single name from another namespace into a local scope, as demonstrated in the following:

using numeric::rational;

A using declaration adds the name to the local scope as though you had declared it explicitly. Thus, within the scope where you place the using declaration, you can use the declared name without qualification (e.g., rational). Listing 52-7 shows how using declarations help avoid the problems you encountered with using directives in Listing 52-6.

Listing 52-7.  Examples of using Declarations with Namespaces

 1 #include <iostream>
 2
 3 void print(int i)
 4 {
 5   std::cout << "int: " << i << ' ';
 6 }
 7
 8 namespace labeled
 9 {
10   void print(double d)
11   {
12     std::cout << "double: " << d << ' ';
13   }
14 }
15
16 namespace simple
17 {
18   void print(int i)
19   {
20     std::cout << i << ' ';
21  }
22   void print(double d)
23   {
24     std::cout << d << ' ';
25   }
26 }
27
28 void test_simple()
29 {
30   using simple::print;
31   print(42);
32   print(3.14159);
33 }
34
35 void test_labeled()
36 {
37   using labeled::print;
38   print(42);
39   print(3.14159);
40 }
41
42 int main()
43 {
44   test_simple();
45   test_labeled();
46 }

Predict the program’s output.

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

This time, the compiler can find simple::print(int), because the using declaration injects names into the local scope. Thus, the local names do not conflict with the global print(int) function. On the other hand, the compiler does not call ::print(int) for line 38. Instead, it calls labeled::print(double), converting 42 to 42.0.

Are you puzzled by the compiler’s behavior? Let me explain. When the compiler tries to resolve an overloaded function or operator name, it looks for the first scope that declares a matching name. It then collects all overloaded names from that scope, and only from that scope. Finally, it resolves the name by choosing the best match (or reporting an error, if it cannot find exactly one good match). Once the compiler finds a match, it stops looking in other scopes or outer namespaces.

In this case, the compiler sees the call to print(42) and looks for the name print first in the local scope, where it finds a function named print that was imported from the labeled namespace. So it stops looking for namespaces and tries to resolve the name print. It finds one function, which takes a double argument. The compiler knows how to convert an int to a double, so it deems this function a match and calls it. The compiler never even looks at the global namespace.

How would you instruct the compiler to also consider the global print function?

_____________________________________________________________

Add a using declaration for the global print function. Between lines 37 and 38, insert the following:

using ::print;

When the compiler tries to resolve print(int), it finds labeled::print(double) and ::print(int), both imported into the local scope. It then resolves the overload by considering both functions. The print(int) function is the best match for an int argument.

Now add using simple::print; at the same location. What do you expect to happen when you compile this example now?

_____________________________________________________________

Now the compiler has too many choices—and they conflict. A using directive doesn’t cause this kind of conflict, because it simply changes the namespaces where the compiler looks for a name. A using declaration, however, adds a declaration to the local scope. If you add too many declarations, those declarations can conflict, and the compiler would complain.

When a using declaration names a template, the template name is brought into the local scope. The compiler keeps track of full and partial specializations of a template. The using declaration affects only whether the compiler finds the template at all. Once it finds the template and decides to instantiate it, the compiler will find the proper specialization. That’s why you can specialize a template that is defined in the standard library—that is, in the std namespace.

A key difference between a using directive and a using declaration is that a using directive does not affect the local scope. A using declaration, however, introduces the unqualified name into the local scope. This means you cannot declare your own name in the same scope. Listing 52-8 illustrates the difference.

Listing 52-8.  Comparing a using Directive with a using Declaration

#include <iostream>
 
void demonstrate_using_directive()
{
   using namespace std;
   typedef int ostream;
   ostream x{0};
   std::cout << x << ' ';
}
 
void demonstrate_using_declaration()
{
   using std::ostream;
   typedef int ostream;
   ostream x{0};
   std::cout << x << ' ';
}

The local declaration of ostream interferes with the using declaration, but not the using directive. A local scope can have only one object or type with a particular name, and a using declaration adds the name to the local scope, whereas a using directive does not.

The using Declaration in a Class

A using declaration can also import a member of a class. This is different from a namespace using declaration, because you can’t just import any old member into any old class, but you can import a name from a base class into a derived class. There are several reasons why you may want to do this. Two immediate reasons are:

  • The base class declares a function, and the derived class declares a function with the same name, and you want overloading to find both functions. The compiler looks for overloads only in a single class scope. With a using declaration to import the base class function into the derived class scope, overloading can find both functions in the derived class scope and so choose the best match.
  • When inheriting privately, you can selectively expose members by placing a using declaration in a public section of the derived class.

Listing 52-9 illustrates using declarations. You will learn more advantages of using declarations as you learn more advanced C++ techniques.

Listing 52-9.  Examples of using Declarations with Classes

#include <iostream>
 
class base
{
public:
  void print(int i) { std::cout << "base: " << i << ' '; }
};
 
class derived1 : public base
{
public:
  void print(double d) { std::cout << "derived: " << d << ' '; }
};
 
class derived2 : public base
{
public:
  using base::print;
  void print(double d) { std::cout << "derived: " << d << ' '; }
};
 
int main()
{
  derived1 d1{};
  derived2 d2{};
 
  d1.print(42);
  d2.print(42);
}

Predict the output from the program.

_____________________________________________________________

_____________________________________________________________

The class derived1 has a single member function named print. Calling d1.print(42) converts 42 to 42.0 and calls that function. Class derived2 imports print from the base class. Thus, overloading determines the best match for d2.print(42) and calls print in the base class. The output appears as follows:

derived: 42
base: 42

Unnamed Namespaces

A name is optional in a namespace definition. The names in an ordinary, named namespace are shared among all files that make up a program, but names in an unnamed namespace are private to the source file that contains the namespace definition.

namespace {
  // Version control ID string is different in every file.
  const std::string id("$Id$");
}

When you want to keep various helper functions and other implementation details private, define them in an unnamed namespace (sometimes called an anonymous namespace). This ensures that their names will not collide with the same names in any other source files. (Note to C programmers: Use anonymous namespaces instead of global static functions and objects.) Restrict your use of the anonymous namespace to source files, not headers. If a header file contains an unnamed namespace, every source file that includes the header is furnished with its own private copy of everything defined in that namespace. Sometimes you want everyone to have a separate, anonymous copy, but usually not.

The only tricky aspect of the unnamed namespace is that you cannot qualify names to refer to names that you defined in the anonymous namespace. You must rely on ordinary name lookup for unqualified names. The next section discusses name lookup issues in greater depth.

Name Lookup

In the absence of namespaces, looking up a function or operator name is simple. The compiler looks in the local block first, then in the outer blocks and the inner namespace before the outer namespace, until, finally, the compiler searches global declarations. It stops searching in the first block that contains a matching declaration. If the compiler is looking for a function or operator, the name may be overloaded, so the compiler considers all the matching names that are declared in the same scope, regardless of parameters.

Looking up a member function is slightly different. When the compiler looks up an unqualified name in a class context, it starts by searching in the local block and enclosing blocks, as described earlier. The search continues by considering members of the class, then its base class, and so on for all ancestor classes. Again, when looking up an overloaded name, the compiler considers all the matching names that it finds in the same scope—that is, the same class or block.

Namespaces complicate the name lookup rules. Suppose you want to use the rational type, which is defined in the exploring_cpp::numeric namespace. You know how to use a qualified name for the type, but what about, for instance, addition or the I/O operators, such as those in the following:

exploring_cpp::numeric::rational<int> r;
std::cout << r + 1 << ' ';

The full name of the addition operator is exploring_cpp::numeric::operator+. But normally, you use the addition operator without specifying the namespace. Therefore, the compiler needs some help to determine which namespace contains the operator declaration. The trick is that the compiler checks the types of the operands and looks for the overloaded operator in the namespaces that contain those types. This is known as argument-dependent lookup (ADL). It is also called Koenig Lookup, after Andrew Koenig, who first described ADL.

The compiler collects several sets of scopes to search. It first determines which scopes to search using the ordinary lookup rules, described at the beginning of this section. For each function argument or operator operand, the compiler also collects a set of namespaces based on the argument types. If a type is a class type, the compiler selects the namespace that contains the class declaration and the namespaces that contain all of its ancestor classes. If the type is a specialization of a class template, the compiler selects the namespace that contains the primary template and the namespaces of all the template arguments. The compiler forms the union of all these scopes and then searches them for the function or operator. As you can see, the goal of ADL is to be inclusive. The compiler tries hard to discover which scope declares the operator or function name.

To better understand the importance of ADL, take a look at Listing 52-10.

Listing 52-10.  Reading and Writing Tokens

#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
 
namespace parser
{
  class token
  {
  public:
    token() : text_{} {}
    token(std::string& s) : text_{s} {}
    token& operator=(std::string const& s) { text_ = s; return *this; }
    std::string text() const { return text_; }
  private:
    std::string text_;
  };
}
 
std::istream& operator>>(std::istream& in, parser::token& tok)
{
  std::string str{};
  if (in >> str)
    tok = str;
  return in;
}
 
std::ostream& operator<<(std::ostream& out, parser::token const& tok)
{
  out << tok.text();
  return out;
}
 
int main()
{
  using namespace parser;
  using namespace std;
 
  vector<token> tokens{};
  copy(istream_iterator<token>(std::cin), istream_iterator<token>(),
       back_inserter(tokens));
  copy(tokens.begin(), tokens.end(), ostream_iterator<token>(cout, " "));
}

What will happen when you compile the program?

_____________________________________________________________

_____________________________________________________________

Some compilers, trying to be helpful, fill your console with messages. The core of the problem is that istream_iterator and ostream_iterator invoke the standard input (>>) and output (<<) operators. In the case of Listing 52-10, the compiler locates the operators through ordinary lookup as member functions of the istream and ostream classes. The standard library declares these member function operators for the built-in types, so the compiler cannot find a match for an argument of type parser::token. Because the compiler finds a match in a class scope, it never gets around to searching the global scope, so it never finds the custom I/O operators.

The compiler applies ADL and searches the parser namespace because the second operand to << and >> has type parser::token. It searches the std namespace because the first operand has type std::istream or std::ostream. It cannot find a match for the I/O operators in these namespaces, because the operators are in the global scope.

Now you see why it’s vital that you declare all associated operators in the same namespace as the main type. If you don’t, the compiler cannot find them. Move the I/O operators into the parser namespace and see that the program now works. Compare your program with Listing 52-11.

Listing 52-11.  Move the I/O Operators into the parser Namespace

#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
 
namespace parser
{
  class token
  {
  public:
    token() : text_{} {}
    token(std::string& s) : text_{s} {}
    token& operator=(std::string const& s) { text_ = s; return *this; }
    std::string text() const { return text_; }
  private:
    std::string text_;
  };
 
  std::istream& operator>>(std::istream& in, parser::token& tok)
  {
    std::string str{};
    if (in >> str)
      tok = str;
    return in;
  }
 
  std::ostream& operator<<(std::ostream& out, parser::token const& tok)
  {
    out << tok.text();
    return out;
  }
}
 
int main()
{
  using namespace parser;
  using namespace std;
 
  vector<token> tokens{};
  copy(istream_iterator<token>(std::cin), istream_iterator<token>(),
       back_inserter(tokens));
  copy(tokens.begin(), tokens.end(), ostream_iterator<token>(cout, " "));
}

To see how the compiler extends its ADL search, modify the program to change the container from a vector to a map, and count the number of occurrences of each token. (Remember Exploration 22?) Because a map stores pair objects, write an output operator that prints pairs of tokens and counts. This means ostream_iterator calls the << operator with two arguments from namespace std. Nonetheless, the compiler finds your operator (in the parser namespace), because the template argument to std::pair is in parser. Your program may end up looking something like Listing 52-12.

Listing 52-12.  Counting Occurrences of Tokens

#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
 
namespace parser
{
  class token
  {
  public:
    token() : text_{} {}
    token(std::string& s) : text_{s} {}
    token& operator=(std::string const& s) { text_ = s; return *this; }
    std::string text() const { return text_; }
  private:
    std::string text_;
  };
 
  // To store tokens in a map.
  bool operator<(token const& a, token const& b)
  {
    return a.text() < b.text();
  }
 
  std::istream& operator>>(std::istream& in, parser::token& tok)
  {
    std::string str{};
    if (in >> str)
      tok = str;
    return in;
  }
 
  std::ostream& operator<<(std::ostream& out, parser::token const& tok)
  {
    out << tok.text();
    return out;
  }
 
  std::ostream& operator<<(std::ostream& out,
                           std::pair<const token, long> const& count)
  {
    out << count.first.text() << ' ' << count.second << ' ';
    return out;
  }
}
 
int main()
{
  using namespace parser;
  using namespace std;
 
  map<token, long> tokens{};
  token tok{};
  while (std::cin >> tok)
     ++tokens[tok];
  copy(tokens.begin(), tokens.end(),
       ostream_iterator<pair<const token, long> >(cout));
}

Now that you know about templates and namespaces, it’s time to look at some of their practical uses. The next several Explorations take a closer look at parts of the standard library, beginning with the standard containers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset