• Advertisement
Sign in to follow this  

Templates, and compilers with strange fancies.

This topic is 4182 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
Just one more stab at getting an answer to my last question.

Share this post


Link to post
Share on other sites
Quote:
Original post by twix
Just one more stab at getting an answer to my last question.


value_type is declared as std::pair< const key_type , mapped_type > which is probably the problem. Try std::pair< typename Container::key_type , typename Container::value_type > instead - hopefully that's convertable (not an expert on all the things std::pair allows :S)

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by twix
Just one more stab at getting an answer to my last question.


value_type is declared as std::pair< const key_type , mapped_type > which is probably the problem. Try std::pair< typename Container::key_type , typename Container::value_type > instead - hopefully that's convertable (not an expert on all the things std::pair allows :S)

Well, I'd like to be able to do this without referring to what the particular value_type in question is. In any case, it doesn't seem like that's the problem. I did some further fiddling, and I'm at a complete loss I'm afraid; I must be completely missing something here.

The full error is:

/usr/include/stlport/stl/_stream_iterator.h:143: error: no match foroperator>>’ in
‘*((_STL::istream_iterator<_STL::pair<const _STL::basic_string<char,
_STL::char_traits<char>, _STL::allocator<char> >, int>, char,
_STL::char_traits<char>, int>*)this)->_STL::istream_iterator<_STL::pair<const
_STL::basic_string<char, _STL::char_traits<char>, _STL::allocator<char> >, int>,
char, _STL::char_traits<char>, int>::_M_stream >>
((_STL::istream_iterator<_STL::pair<const _STL::basic_string<char,
_STL::char_traits<char>, _STL::allocator<char> >, int>, char,
_STL::char_traits<char>, int>*)this)->_STL::istream_iterator<_STL::pair<const
_STL::basic_string<char, _STL::char_traits<char>, _STL::allocator<char> >, int>,
char, _STL::char_traits<char>, int>::_M_value’




OK... so I try to emulate the exact source that would produce this error.

typedef
std::istream_iterator<
std::pair<
const std::basic_string<char, std::char_traits<char>, std::allocator<char> >,
int
>,
char,
std::char_traits<char>,
int
> iter_type;

iter_type my_iterator;
iter_type::value_type my_value;
iter_type::istream_type* my_stream = new iter_type::istream_type(std::cin.rdbuf());

*my_stream >> my_value;


delete my_stream;

std::cout << my_value << std::endl;



Unfortunately(?), this snippet compiles and works fine. What exactly did I do differently?

Share this post


Link to post
Share on other sites
Quote:
Original post by twix
Unfortunately(?), this snippet compiles and works fine. What exactly did I do differently?


I'm not entirely sure. That said, your code is almost certainly doing something in error if it's able to read in/change the constant .first member (or at least const-safety inappropriate). What's the code you're using to read in pairs?

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by twix
Unfortunately(?), this snippet compiles and works fine. What exactly did I do differently?


I'm not entirely sure. That said, your code is almost certainly doing something in error if it's able to read in/change the constant .first member (or at least const-safety inappropriate). What's the code you're using to read in pairs?

To deal with the issue you brought up, I'm forcibly deconstifying pair members. I don't know if there's a better way to handle this, but I'll stick with this method till I can solve the bigger problem...

template< typename T1, typename T2 >
std::istream& operator>>(std::istream& is, std::pair<T1, T2>& result)
{
typedef typename std::tr1::remove_const<T1>::type T1_nonconst;
typedef typename std::tr1::remove_const<T2>::type T2_nonconst;

T1_nonconst first;
is >> first;

matchString(is, ":");

T2_nonconst second;
is >> second;

const_cast<T1_nonconst&>(result.first) = first;
const_cast<T2_nonconst&>(result.second) = second;

return is;
}

Share this post


Link to post
Share on other sites
Well, I guess this must be a fairly nontrivial problem. It may not be worth bothering with; at this point, I can just use this workaround:

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;

matchString(is, ")");
matchString(is, "{");

typename std::insert_iterator<Container> iiter(container, container.begin());

for (unsigned int i = 0; i < size; i++)
{
typename Container::value_type value;
is >> value;

*iiter++ = value;
matchString(is, ",");
}

matchString(is, "}");

return is;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by twix
Quote:
Original post by MaulingMonkey
Quote:
Original post by twix
Unfortunately(?), this snippet compiles and works fine. What exactly did I do differently?


I'm not entirely sure. That said, your code is almost certainly doing something in error if it's able to read in/change the constant .first member (or at least const-safety inappropriate). What's the code you're using to read in pairs?

To deal with the issue you brought up, I'm forcibly deconstifying pair members. I don't know if there's a better way to handle this,


Scary. I really recommend the pair< key_type , mapped_type > in favor of this. If you really need to avoid binding only to interfaces exposing a pair or key/mapped_type, a workaround would be:

template < typename T > struct remove_pair_const {
typedef typename std::tr1::remove_const< T >::type type;
};
template < typename L , typename R >
struct remove_pair_const< std::pair< L , R > > {
typedef std::pair< typename std::tr1::remove_const< L >::type
, typename std::tr1::remove_const< R >::type
> type;
};

...

std::copy_n( std::istream_iterator<typename remove_pair_const< typename Container::value_type >::type >(is)
, size
, std::insert_iterator<Container>(container, container.begin())
//(should work with value_type != pair<...>
);


Your operator>> const hack only addresses constness (in a dangerous manner) for istream reading - assignment will still fail, which *might* be part of what's botching up this situation.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by twix
Quote:
Original post by MaulingMonkey
Quote:
Original post by twix
Unfortunately(?), this snippet compiles and works fine. What exactly did I do differently?


I'm not entirely sure. That said, your code is almost certainly doing something in error if it's able to read in/change the constant .first member (or at least const-safety inappropriate). What's the code you're using to read in pairs?

To deal with the issue you brought up, I'm forcibly deconstifying pair members. I don't know if there's a better way to handle this,


Scary. I really recommend the pair< key_type , mapped_type > in favor of this. If you really need to avoid binding only to interfaces exposing a pair or key/mapped_type, a workaround would be:

...

Your operator>> const hack only addresses constness (in a dangerous manner) for istream reading - assignment will still fail, which *might* be part of what's botching up this situation.

That doesn't fix the lack of operator>> problem, but I've pretty much given up on that. *shrug*

I'll use your idea for const-removal, though, since it does seem to have a slightly less sinister air to it.

Anyway, thanks for your help. Props to your C++ wizardliness. [smile]

Share this post


Link to post
Share on other sites
After hacking together a lot of boilerplate (including switching from the nonstandard copy_n to the standard generate_n which VS2005 supports, and rolling hand-made implementations of tr1::remove_const since I can't seem to find which header if any VS2005 has for that TR1 function), I've figured out the problem. I've figured out a hackerish solution. I've not figured out the clean solution.

#include <boost/utility.hpp>
#include <iostream>
#include <utility>
#include <algorithm>
#include <iterator>
#include <vector>
#include <map>
#include <string>
#include <cassert>

void matchString( std::istream & is , const std::string & text ) {
std::vector< char > buffer( text.size() );
is.read( &(buffer[0]) , std::streamsize(text.size()) );
assert( text == std::string( buffer.begin() , buffer.end() ) );
}

namespace std {
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 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 T >
class istream_generator {
std::istream_iterator< T > iter;
public:
istream_generator( std::istream & is ) : iter( is ) {}
T operator()() { return *(iter++); }
};

template < typename T > struct remove_const { typedef T type; };
template < typename T > struct remove_const< const T > { typedef T type; };

template < typename T > struct remove_pair_const {
typedef typename remove_const< T >::type type;
};
template < typename L , typename R >
struct remove_pair_const< std::pair< L , R > > {
typedef std::pair< typename remove_const< L >::type
, typename remove_const< R >::type
> type;
};

template< typename Container >
typename boost::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()) );
std::generate_n( std::insert_iterator< Container >( container , container.begin() ) , size
, istream_generator< typename remove_pair_const< typename Container::value_type >::type >( is )
);

matchString(is, "}");

return is;
}

int main() {
std::map< int , int > data;
std::cin >> data;
}





Eww. Anyways - removing the namespace std { ... } around the pair operator>> causes compilation to fail in the way originally described. I deduced this to be the problem after compiler errors did not list that version under the "could be ... or ... or ..." list. Since all operands clearly reside within the std:: namespace (not a problem with the container-templated version, apparently), and operator>> is named from within the std:: namespace (within std::istream_iterator), the compiler makes no attempt to look outside the std:: namespace (since the operators there shadow those in the global namespace).

The solution, then, relies only on getting that operator>> back into the list of considerations. Adding it within the std:: namespace in the first place accomplishes this, but is likely in violation of a certain C++ rule - namely, you're not supposed/legally allowed to add to namespace std. The exception to this rule is specializing already existing items, but I was under the impression that operator>>( *intrinsicly supported types* ) was implemented in terms of a member function of std::istream, so I'm not sure that applies here.

You could probably work around this with an enable_if< is_a_pair<T>, .... > qualifier outside of the std namespace, but I'll leave that as an exercise to the reader. I'll leave elaborating on wheither this example is valid C++ to someone more standard-aware than myself.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
After hacking together a lot of boilerplate (including switching from the nonstandard copy_n to the standard generate_n which VS2005 supports, and rolling hand-made implementations of tr1::remove_const since I can't seem to find which header if any VS2005 has for that TR1 function), I've figured out the problem. I've figured out a hackerish solution. I've not figured out the clean solution.

*** Source Snippet Removed ***

Eww. Anyways - removing the namespace std { ... } around the pair operator>> causes compilation to fail in the way originally described. I deduced this to be the problem after compiler errors did not list that version under the "could be ... or ... or ..." list. Since all operands clearly reside within the std:: namespace (not a problem with the container-templated version, apparently), and operator>> is named from within the std:: namespace (within std::istream_iterator), the compiler makes no attempt to look outside the std:: namespace (since the operators there shadow those in the global namespace).

The solution, then, relies only on getting that operator>> back into the list of considerations. Adding it within the std:: namespace in the first place accomplishes this, but is likely in violation of a certain C++ rule - namely, you're not supposed/legally allowed to add to namespace std. The exception to this rule is specializing already existing items, but I was under the impression that operator>>( *intrinsicly supported types* ) was implemented in terms of a member function of std::istream, so I'm not sure that applies here.

You could probably work around this with an enable_if< is_a_pair<T>, .... > qualifier outside of the std namespace, but I'll leave that as an exercise to the reader. I'll leave elaborating on wheither this example is valid C++ to someone more standard-aware than myself.

You sir, are a magician.

Share this post


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

  • Advertisement