Templates, and compilers with strange fancies.

Started by
19 comments, last by MaulingMonkey 17 years, 8 months ago
Here's a function.

template
<
	typename _Key, typename _Value,
	template <typename K, typename V> class _Map_type
>
std::ostream& operator<<(std::ostream& os, _Map_type<_Key, _Value> const& map)
{	
	os << "map(" << map.size() << ") {\n";
	
	for(typename _Map_type<_Key, _Value>::const_iterator i = map.begin();
		i != map.end(); ++i)
	{
		typename _Map_type<_Key, _Value>::key_type key_data = i->first;
		typename _Map_type<_Key, _Value>::data_type value_data = i->second;
		
		os << key_data << " : " << value_data << " ,\n";
	}
	
	return os << " }";
}

Now it seems to me, that given the declarations of the key_data and value_data variables, this function should only be able to take parameters that fit the STL pair associative container interface. However, my compiler (GCC 4.1.1) happily substitutes std::basic_string for _Map_type and then complains about ambiguous calls to operator<<. What did I do wrong?
Advertisement
Quote:Original post by twix
owever, my compiler (GCC 4.1.1) happily substitutes std::basic_string for _Map_type and then complains about ambiguous calls to operator<<. What did I do wrong?


When applying the rules for function overloading, the compiler does not take the function implementation into account (in fact, it's not necessarily even in scope). It only takes the function signature into account. The standard string class will also take two template parameters.

One way for force a disambiguation is to use a forwarding class that has a contructor that takes your _Map_type (an illegal name, by the way) and implement your stream insertion operator in terms of the forwarding class. Ugly, but it's how an awful lot of the standard library is implemented.

For an example, look at how the <iomanip> header implements iomanipulator calls.

Stephen M. Webb
Professional Free Software Developer

Oh. That's... annoying. What's wrong with the name _Map_type?

I'm not very familiar with C++, and I've never actually used more than the very basics of templates before, so I'm in rather strange territory here. Let me see if I understand you... you mean I should make a class something like this?
template<	typename _Key, typename _Value,	template <typename K, typename V> class _Map_type>class AForwardingClass{public:	AForwardingClass(_Map_type<_Key, _Value> const& value)	{		this->value = value;	}	_Map_type<_Key, _Value> const& getValue() { return value; }	private:	_Map_type<_Key, _Value> const& value;		typename _Map_type<_Key, _Value>::key_type dummy_variable_1;	typename _Map_type<_Key, _Value>::data_type dummy_variable_2;};

Also, is there a simple way to get the size of an array, that will work for both standard C++ arrays and any STL ordered sequence container (including list, deque, etc.)? Performance is not an issue, so explicitly counting the elements is OK.

My initial thought is to use count_if with a function that always returns true, but I'd like to keep the kludgery to an absolute minimum, so...
Quote:
That's... annoying. What's wrong with the name _Map_type?


It might be annoying for you, but its probably more annoying for others in general if it worked the other way around.

_Map_type is illegal because identifiers beginning with an underscore are reserved, in one form or another, by the compiler. The specifics depend on scope, whether you're doing C or C++, and other such things, but basically you should never construct an identifier starting with an underscore if you want to be safe and fully compliant.

Quote:
Also, is there a simple way to get the size of an array, that will work for both standard C++ arrays and any STL ordered sequence container (including list, deque, etc.)? Performance is not an issue, so explicitly counting the elements is OK.

My initial thought is to use count_if with a function that always returns true, but I'd like to keep the kludgery to an absolute minimum, so...


Not really. count_if() won't work because you'd need a begin and end iterator for the sequence, and you can't really get an end iterator for a regular array (which carries zero size information) without knowing its size already.
Quote:Original post by twix
What's wrong with the name _Map_type?


Identifiers starting with a double underscore, or an underscore followed by a capital letter, are reserved for the implementation in all scopes.

Identifiers starting with an underscore followed by a lowercase letter are also reserved in the file/root scope or some such.

Given that neither add any useful information, there's really no reason to use these extra underscores anyways.

Personally, I'd abuse boost/SFINAE here. Compiling version of your code:

#include <boost/utility/enable_if.hpp>#include <string>#include <map>#include <iostream>template < typename T > struct is_pair{ static const bool value = false; };template < typename LHS , typename RHS >struct is_pair< std::pair< LHS , RHS > >{ static const bool value = true; };template < typename MapT >typename boost::enable_if< is_pair< typename MapT::value_type > , std::ostream >::type & operator<<(std::ostream& os, MapT const& map){		os << "map(" << map.size() << ") {\n";		for(typename MapT::const_iterator i = map.begin();		i != map.end(); ++i)	{		typename MapT::key_type key_data = i->first;		typename MapT::mapped_type value_data = i->second;				os << key_data << " : " << value_data << " ,\n";	}		return os << " }";}int main () {	std::map< char , int > map;	map[ 'a' ] = 42;	map[ 'b' ] = 33;		std::string pie = "pie";		std::cout << map << "\n" << pie << std::endl;}


This should work for any MapT which typedefs value_type as some sort of std::pair (nicely excluding std::string).

Notes:
1) mapped_type, not data_type.
2) http://boost.org/ = win

The underlying mechanics of boost::enable_if are pretty trivial. Suffice it to say, in this example, enable_if< Condition , T >::type will be typedefed to T if Condition::value == true, and won't be defined at all if Condition::value == false - thus not forming a valid function, thus not being considered (and thus not possibly creating an amgiguity).

EDIT: Switching key_type and mapped_type to "value_type::typename first_type" and "value_type::typename second_type" would allow this to be used with, say, std::vector< std::pair< char , int > > as well. Just something to mull over.
Quote:Original post by twix
Also, is there a simple way to get the size of an array, that will work for both standard C++ arrays and any STL ordered sequence container (including list, deque, etc.)? Performance is not an issue, so explicitly counting the elements is OK.

If you have the array variable or a reference to it at hand, yes. If you only have a pointer to the array, no. Such is life.

template <typename Container>std::size_t size(Container const& c){  return c.size();}template <std::size_t size, typename T>std::size_t size(T (&array)[size]){  return size;}int main(){  std::vector<int> vec(123);  float arr[456];  float *parr = arr;  assert(size(vec) == 123);  assert(size(arr) == 456);  //calling size(parr) would result in compile-time error}
Thanks everybody. Ratings increases for all.

Stealing your various ideas, I think I'll go with the following, since it seems very generic:
template <typename T> struct non_string_iterable{ 	template <typename U>	static long  test_iterator(U*, typename U::iterator* iter = 0);	static short test_iterator( ... );		static const bool value = ( sizeof(test_iterator((T*)0)) == sizeof(long) );};template <typename T> struct non_string_iterable< std::basic_string<T> >{	static const bool value = false;};template< typename T1, typename T2 >std::ostream& operator<<(std::ostream& os, std::pair<T1, T2> value){	return os << value.first << " : " << value.second;}template< typename Container >typename enable_if< non_string_iterable<Container>, std::ostream >::type &operator<<(std::ostream& os, Container const& container){		os << "collection(" << container.size() << ") {\n";		for(typename Container::const_iterator i = container.begin();		i != container.end(); ++i)		os << (*i) << " ,\n";			return os << " }\n";}

It'll work for any STL container except for std::string, which is what I was really going for, anyway.
Well, turns out I have another problem. Since it's basically about the same thing, I'll keep it in this thread.

Now I'm trying to read back the text representation of my containers. So I've got these functions:
template< typename T1, typename T2 >std::istream& operator>>(std::istream& is, std::pair<T1, T2>& result){	is >> result.first;	// VALIDATE(is, pair_first);		matchString(is, ":");		is >> result.second;	// VALIDATE(is, pair_second);		return is;}template< typename Container >typename enable_if< non_string_iterable<Container>, std::istream >::type &operator>>(std::istream& is, Container& container){	matchString(is, "collection");	matchString(is, "(");		unsigned int size;	is >> size;	// VALIDATE(is, array_size);		std::copy_n( std::istream_iterator<typename Container::value_type>(is),                     size, std::insert_iterator<Container>(container, container.begin()) );				 	matchString(is, "}");		return is;}

I'm using copy_n since I don't know any other way to generically fill in any container. Unfortunately, my compiler chokes inside the istream_iterator code when I try to fill in a map, claiming that there's no applicable operator>> for std::pairs. I can read into a std::pair from cin, though, so I'm a bit confused.
Quote:Original post by jpetrie
Quote:
Also, is there a simple way to get the size of an array, that will work for both standard C++ arrays and any STL ordered sequence container (including list, deque, etc.)? Performance is not an issue, so explicitly counting the elements is OK.

My initial thought is to use count_if with a function that always returns true, but I'd like to keep the kludgery to an absolute minimum, so...


Not really. count_if() won't work because you'd need a begin and end iterator for the sequence, and you can't really get an end iterator for a regular array (which carries zero size information) without knowing its size already.


Consequently: Don't try to "get" the size of the array. Tell, don't ask, by having your function accept an iterator range (two iterator parameters representing beginning and one-past-end, just like count_if does - not to mention lots of other std:: algorithms). If you need to "remember" that information, then you should be transporting it along with the array somehow: you are strongly recommended to use either std::vector or boost::array, depending on your needs.

This topic is closed to new replies.

Advertisement