Dear Editor,

 

Sven Rosvall’s “C++ Lookup Mysteries” in Overload 63 couldn’t have been better timed as it provided a solution to a problem I had been struggling with -  a test harness that failed to compile after a new feature was added to the main product because of C++’s non-intuitive name lookup rules.

 

The problematic code, trimmed to the minimum to illustrate the problem, was:

 

template<class C>

void DebugPrint(const string& description, const C& container)

{

      cout << description << "\n";

      //some stuff

      copy(container.begin(),

container.end(),

ostream_iterator<typename C::iterator::value_type>(cout, " "));

      cout << endl;

      //some other stuff

}

 

This works fine for standard containers containing either built-in types or user defined types that define an output operator in the same namespace as the type is defined. The code that broke the test harness was a standard container of std::pair. The obvious solution, defining operator<<(std::pair) in the test harness namespace, didn’t work because the compiler cannot “see” this definition. The problem is that operator<< is already defined (for the built-in types) in namespace std and masks my definition, as Sven explains “Firstly, the nearest enclosing namespace is searched for ‘entities’ with the same name. Note that as soon as a name is found the search stops” (my italics). C++ name lookup says that the only place that the compiler will look for operator<<(std::pair) is in std.

 

Ah, so all I have to do is define it in std:

 

namespace std {

template<class T1, class T2>

std::ostream& operator<<(std::ostream& os, const std::pair<T1,T2>& p) {return os<<"("<<p.first<<","<<p.second<<")"; }

}

 

except that adding declarations or definitions to namespace std is undefined behaviour according to the standard (Clause 17.4.3.1).

 

Of course the name lookup will find operator<<(pair) in the test harness namespace for a pair also in that namespace. The standard fully defines std::pair (Clause 20.2.2) so I can copy the source code to define my own pair (in my workspace) and expect identical behaviour. Although legal, this has a number of problems:

 

To force the name lookup to find my operator<<(std::pair) without duplicating std::pair, I took Sven’s wrapper class, PrintSpannerNameAndGap, and made it into a template class and output function:

 

template<class T>

class osformatter {

public:

      osformatter(const T& t) : t_(t) {}

      void print(std::ostream& os) const {

            os << t_;

      }

private:

      const T & t_;

};

 

template<class T>

std::ostream& operator<<(std::ostream& os, const osformatter<T>& f) {

      f.print(os);

      return os;

}

 

and changed the line in the debug function to use it:

 

      copy(container.begin(),

container.end(),

ostream_iterator<osformatter<typename C::iterator::value_type> >(cout, " "));

 

This now works for all built-in types and any user defined types that define an operator<< in the same namespace.

 

Now it is possible to write a specialisation of osformatter for any type that does not support the output operator or for which we want some special formatting, for example, fixed precision doubles:

 

class osformatter<double> {

public:

      osformatter(const double& d) : d_(d) {}

      void print(std::ostream& os) const {

            int p=os.precision();

            os.precision(4);

            os << d_;

            os.precision(p);

      }

private:

      const double& d_;

};

 

I can now apply the same specialisation to std::pair, which, being a template itself,  needs a template declaration for the types contained within it:

 

template<class T1, class T2>

class osformatter<std::pair<T1,T2> > {

public:

      osformatter(const std::pair<T1,T2>& p) : p_(p) {}

      void print(std::ostream& os) const {

            os << "(" << p_.first << "," << p_.second << ")";

      }

private:

      const std::pair<T1,T2>& p_;

};

 

This now compiles because the std::pair output code is contained in osformatter, and thus explicitly called, and so is no longer dependent on the name lookup rules.

 

This not only solved my name lookup problem but provided a nice way of changing the default output format of build-in types when using copy.

 

Regards,

Mark Easterbrook

mark@easterbrook.org.uk

 

 

 

 

 

.uk">mark@easterbrook.org.uk

 

 

 

 

 

ody> >