Jump to content
  • Advertisement
Sign in to follow this  
twix

Templates, and compilers with strange fancies.

This topic is 4448 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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;
};


Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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
}

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!