Trying to apply SFINAE

H

Hendrik Schober

Hi,

I'm having two overloaded function templates,

#include <iterator>

template< typename T >
void test( T /*a1*/, T /*a2*/ ) {}

template< typename Iter >
void test( Iter /*b*/, Iter /*e*/ ) {}

which I need to call. (In reality, these are constructors,
in case that matters.) Unfortunately, the compiler isn't
as clever and can't tell 'T' from 'Iter', so I have to
give it a hint whether what gets passed is an iterator.
But my attempt to use SFINAE for this

template< typename Iter, class ItTr >
void test( Iter /*b*/, Iter /*e*/
, ItTr = std::iterator_traits<Iter>() ) {}

doesn't work, since this completely removed the iterator
overload from the set the compiler considered. Or that's
what I figured because this

#include <iterator>

//template< typename T >
//void test( T /*a1*/, T /*a2*/ ) {}

template< typename Iter, class ItTr >
void test( Iter /*b*/, Iter /*e*/
, ItTr = std::iterator_traits<Iter>() ) {}

int main()
{
const int fa[] = { 255, 255, 255, 255 };

//test(0,1);
test(fa, fa+4);

return 0;
}

doesn't compile.
Now I'm stumped. I thought I had done this before, so I am
probably making something stupid here. (It's already passed
6pm here...)

Anyone out there?

TIA,

Schobi
 
J

James Kanze

I'm having two overloaded function templates,
#include <iterator>
template< typename T >
void test( T /*a1*/, T /*a2*/ ) {}
template< typename Iter >
void test( Iter /*b*/, Iter /*e*/ ) {}
which I need to call. (In reality, these are constructors,
in case that matters.)

It could be critical, if you need the initialization list.
Unfortunately, the compiler isn't as clever and can't tell 'T'
from 'Iter', so I have to give it a hint whether what gets
passed is an iterator. But my attempt to use SFINAE for this
template< typename Iter, class ItTr >
void test( Iter /*b*/, Iter /*e*/
, ItTr = std::iterator_traits<Iter>() ) {}
doesn't work, since this completely removed the iterator
overload from the set the compiler considered.

For overloading purposes, this is two functions, one with three
arguments, and one with two. The one with two puts you right
back where you started. The usual solution here (with
functions) is to have a single function, which then calls some
other function with an additional argument, usually 0; the other
function is overloaded on this additional argument: one of the
overloads will be a template which requires the targetted
condition in order for type deduction to succeed, and the other
is ... (which of course, matches anything, but is at the bottom
of the pile with regards to overload resolution).

Note too that there is absolutely no guarantee that attempting
to instantiat iterator_traits over something that is not an
iterator will fail. And even if it does, you need something
that requires that it be expanded. The most obvious choice
would be something like:

template said:
::iterator_category* )
{
// Case iterator ...
}

template< typename T >
void testHelper( T, ... )
{
// Case non-iterator...
}

template< typename T >
void test( T a )
{
testHelper( a, 0 ) ;
}

This doesn't seem to work, however, at least not with g++ 4.1.0;
the compiler doesn't seem to ever consider the first function.
(The T in the second argument is in a non-deduced context, but I
would have thought that having deduced it from the first
argument, the compiler would simply try to use it in the second;
if it could instantiation std::iterator_traits with this T, and
the resulting instantiation contained a type named
iterator_category, that type deduction would succeed.
Curiously, even if I replace
std::iterator_traits< T >::iterator_category*
with simply:
std::iterator_traits< T >*
, type deduction still seems to fail. There's something here I
don't understand.

But of course, since it's undefined behavior to instantiate
iterator_trais with anything that is neither an iterator nor a
pointer, you couldn't count on this even if the type deduction
trick worked.
 
H

Hendrik Schober

Victor said:
Hendrik Schober wrote:
[...]

Well, the compiler cannot deduce the type from a default argument, I
vaguely recall that it's a non-deducible context. That's why putting
the traits there won't cut it. Consider

#include <iterator>

template<typename Iter>
void test(Iter, Iter, std::iterator_traits<Iter> const* = 0);

int main ()
{
const int fa[] = { 1,2,3,4 };
test(fa, fa+4);
}

Thanks! However, while the above works, this doesn't:

#include <iterator>

template< typename T >
void test( T /*a1*/, T /*a2*/ ) {}

template< typename Iter >
void test( Iter /*b*/, Iter /*e*/
, std::iterator_traits<Iter> = std::iterator_traits<Iter>() ) {}

int main()
{
const int fa[] = { 255, 255, 255, 255 };

test(0,1);
test(fa, fa+4);

return 0;
}

(Both VC and Comeau complain it's ambiguous.) But that's
just what I need.

Schobi
 
H

Hendrik Schober

James said:
It could be critical, if you need the initialization list.

The problem is more with your below idea: I cannot
(easily) forward calls.
[...]
But of course, since it's undefined behavior to instantiate
iterator_trais with anything that is neither an iterator nor a
pointer, you couldn't count on this even if the type deduction
trick worked.

Is it?
I thought std lib implementors must have faced this,
but now I see that this

#include <vector>

int main()
{
const unsigned int ua[] = { 255, 255, 255, 255 };

std::vector<int> v1(0u,1u);
std::vector<int> v2(ua, ua+4);

return 0;
}

fails spectacularly with Dinkumware (both call the ctor
taking iterators), so I must be wrong.

Well, it seems I'm back to square one, then.
Any ideas out there?

Schobi
 
F

Francesco

Hi to all,
maybe something like the following might work.
Relying on the fact that an iterator should be either
- a pointer
- a class with some inner typedefs (here iterator_category)
The inspector class can be customized to better suit your needs...
Maybe there is a simpler way... don't know :)
Bye,
Francesco

// code
#include <iterator>
#include <iostream>
#include <vector>

// ala boost::enable_if
template< typename T, bool K >
class CTypeEnable;

template< typename T >
class CTypeEnable< T, false >
{};

template< typename T >
class CTypeEnable< T, true >
{
public:
typedef T tResult;
};

// ala boost::??
template< typename T >
struct CIsIter
{
typedef char (&tYes)[1];
typedef char (&tNo)[2];

template< typename T2 >
static typename CTypeEnable< tYes,
sizeof( typename T2::iterator_category ) >::tResult
Check( T2 * );

template< typename T2 >
static tYes Check( T2 ** );

static tNo Check( ... );

enum { kResult = sizeof( Check( (T*) NULL ) )
== sizeof( tYes ) };
};

// SFINAE with return type

template< typename T >
typename CTypeEnable< void, !CIsIter< T >::kResult >::tResult F( T )
{ std::cout << "NO ITERATOR\n"; }

template< typename T >
typename CTypeEnable< void, CIsIter< T >::kResult >::tResult F( T )
{ std::cout << "ITERATOR\n"; }

struct A
{};

int main()
{
F( 10 );
F( ( int*)NULL);
std::vector< int > vec;
F( vec.begin() );
F( A() );
}
//end code

Hendrik Schober ha scritto:
James said:
It could be critical, if you need the initialization list.

The problem is more with your below idea: I cannot
(easily) forward calls.
[...]
But of course, since it's undefined behavior to instantiate
iterator_trais with anything that is neither an iterator nor a
pointer, you couldn't count on this even if the type deduction
trick worked.

Is it?
I thought std lib implementors must have faced this,
but now I see that this

#include <vector>

int main()
{
const unsigned int ua[] = { 255, 255, 255, 255 };

std::vector<int> v1(0u,1u);
std::vector<int> v2(ua, ua+4);

return 0;
}

fails spectacularly with Dinkumware (both call the ctor
taking iterators), so I must be wrong.

Well, it seems I'm back to square one, then.
Any ideas out there?

Schobi
 
J

James Kanze

The problem is more with your below idea: I cannot
(easily) forward calls.

That's why I raised the issue of initialization lists. That's
the main reason I can think of why you might not be able to
forward calls. (It is, in fact, the only reason I can think of
why you might not be able to forward calls.)
[...]
But of course, since it's undefined behavior to instantiate
iterator_trais with anything that is neither an iterator nor a
pointer, you couldn't count on this even if the type deduction
trick worked.

I think so. In §17.4.3.6/2, it says:

In particular, the effects are undefined in the
following cases:
[...]
-- for types used as template arguments when
instantiating a template component, if the
operations on the type do not implement the
semantics of the applicable Requirements
subclause.[...]

I'm not sure, of course, because the description of Iterator
traits (§24.3.1) doesn't actually contain a Requirements
subclause. It does require a specific implementation, however
(with a partial specialization for pointers), and that
implementation uses typename Iterator::difference_type, etc. So
the presence of those types in the instantiation type would seem
to be a requirement. (In this regared, the definition of
iterator_category is probably the most significant; I can't
imagine anything but an iterator defining it.)

In practice, what I would expect is that the code would fail to
compile *IF* you use such an instantiation in a context which
requires a complete definition. Or, if that failure occured
during template type deduction, SFINAE. But I couldn't get it
to work.
I thought std lib implementors must have faced this,
but now I see that this
#include <vector>
int main()
{
const unsigned int ua[] = { 255, 255, 255, 255 };
std::vector<int> v1(0u,1u);
std::vector<int> v2(ua, ua+4);
return 0;
}
fails spectacularly with Dinkumware (both call the ctor
taking iterators), so I must be wrong.

The second is required to call the constructor taking iterators.
The first shouldn't, however; the standard says that if the
iterator type (determined by type deduction) is an integral
type, the constructor shall has the same effect as:

X( static_cast< typename X::size_type >( f ),
static_cast< typename X::value_type >( l ) ) ;

(This leads to some interesting, and possibly unintentional
effects, due to the fact that static_cast is an explicit
conversion. Thus:

std::vector< std::vector< int > > v( 10, 30 ) ;

works, although there is no implicit conversion of 30 to
std::vector< int >.)

The version of VC++ (which uses Dinkumware) which I have access
to gets this right. Some older implementations of the library
(Dinkumware or others), designed for compilers which didn't
support member templates, may have problems, however.
 
H

Hendrik Schober

Victor said:
Hendrik Schober wrote:
[...]
(Both VC and Comeau complain it's ambiguous.) But that's
just what I need.

They are probably correct :-/

Let's try to involve a class where you can make a partial specialisation:
[example snipped]

I knew how to solve this with introducing forwarding functions
or additional class templates. I didn't want to change the code
so much.
Thanks nevertheless!

Schobi
 
H

Hendrik Schober

James said:
That's why I raised the issue of initialization lists. That's
the main reason I can think of why you might not be able to
forward calls. (It is, in fact, the only reason I can think of
why you might not be able to forward calls.)

Ah, mea culpa. I didn't get this.
[...]
But of course, since it's undefined behavior to instantiate
iterator_trais with anything that is neither an iterator nor a
pointer, you couldn't count on this even if the type deduction
trick worked.

I think so. In §17.4.3.6/2, it says:
[explanation snipped]

Thanks!
I thought std lib implementors must have faced this,
but now I see that this
#include <vector>
int main()
{
const unsigned int ua[] = { 255, 255, 255, 255 };
std::vector<int> v1(0u,1u);
std::vector<int> v2(ua, ua+4);
return 0;
}
fails spectacularly with Dinkumware (both call the ctor
taking iterators), so I must be wrong.

[...]

The version of VC++ (which uses Dinkumware) which I have access
to gets this right. Some older implementations of the library
(Dinkumware or others), designed for compilers which didn't
support member templates, may have problems, however.

I tried with VC9 (2008) and VC7.1 (2003). Both /seemed/ to fail,
but now that I dug deeper, I know that, while it indeed calls
the wrong ctor, that in turn forwards to some other function
and (by use of iterator tags) picks an overload of that which
does the right thing.
I'm sorry for spreading false information. :-x

Schobi
 
H

Hendrik Schober

Francesco said:
Hi to all,
maybe something like the following might work.
[a lot of clever code snipped]

It might (except that ctors don't have a return type, but
that's curable), but it would introduce a lot of template
meta stuff into the code. This whole thing only came up
because I eliminated a bunch of warnings and I'm reluctant
to change too much well-tested code (I'd have to change
dozens of classes) at the heart of a quite sophisticated
domain-specific library I'm not very familiar with yet just
to get rid of a few warnings.
Thanks anyway. Yours is an interesting solution. It does
remind me that I still have to lobby for boost ('enable_if')
here. :)

Schobi
 
K

Kai-Uwe Bux

Francesco said:
Hi to all,
maybe something like the following might work.
Relying on the fact that an iterator should be either
- a pointer
- a class with some inner typedefs (here iterator_category)
The inspector class can be customized to better suit your needs...
Maybe there is a simpler way... don't know :)
Bye,
Francesco

// code
#include <iterator>
#include <iostream>
#include <vector>

// ala boost::enable_if
template< typename T, bool K >
class CTypeEnable;

template< typename T >
class CTypeEnable< T, false >
{};

template< typename T >
class CTypeEnable< T, true >
{
public:
typedef T tResult;
};

// ala boost::??
template< typename T >
struct CIsIter
{
typedef char (&tYes)[1];
typedef char (&tNo)[2];

template< typename T2 >
static typename CTypeEnable< tYes,
sizeof( typename T2::iterator_category ) >::tResult
Check( T2 * );

template< typename T2 >
static tYes Check( T2 ** );

static tNo Check( ... );

enum { kResult = sizeof( Check( (T*) NULL ) )
== sizeof( tYes ) };
};

// SFINAE with return type

template< typename T >
typename CTypeEnable< void, !CIsIter< T >::kResult >::tResult F( T )
{ std::cout << "NO ITERATOR\n"; }

template< typename T >
typename CTypeEnable< void, CIsIter< T >::kResult >::tResult F( T )
{ std::cout << "ITERATOR\n"; }

struct A
{};

int main()
{
F( 10 );
F( ( int*)NULL);
std::vector< int > vec;
F( vec.begin() );
F( A() );
}
//end code


That idea can work a little simpler:


struct yes_type { char dummy; };
struct no_type { yes_type a; yes_type b; };

template < typename T >
struct is_iterator_type {

template < typename S >
static
yes_type check ( S*, typename S::iterator_category* ptr = 0 );

template < typename S >
static
yes_type check ( S** );

static
no_type check ( ... );

public:

static bool const value =
sizeof( check((T*)0) ) == sizeof( yes_type );

};


#include <iostream>
#include <vector>

template < typename T >
void show ( void ) {
if ( is_iterator_type<T>::value ) {
std::cout << "is iterator type\n";
} else
std::cout << "is not iterator type\n";
}

#define SHOW(T) \
std::cout << #T << " "; \
show<T>();

int main() {
SHOW( std::vector<int>::const_iterator );
SHOW( std::vector<int> );
SHOW( int* );
SHOW( int );
}


Best

Kai-Uwe Bux
 
H

Hendrik Schober

Kai-Uwe Bux said:
Francesco wrote:
[...]

That idea can work a little simpler:

[code snipped]

In my case it's even simpler, since I don't need the meta-function.
Here's what I came up with while commuting this morning:

struct yes_type { char dummy; };
struct no_type { char dummy1; char dummy2; };

template < typename T >
yes_type is_iterator(const T&, typename T::iterator_category* = 0)
{return yes_type();}

template < typename T >
yes_type is_iterator(const T* const)
{return yes_type();}

no_type is_iterator(const void* const)
{return no_type();}

no_type is_iterator(...)
{return no_type();}


#include <iostream>
#include <vector>

template < typename T >
inline void show( const T&, yes_type /*is_iterator*/)
{std::cout << "iterator: ";}

template < typename T >
inline void show( const T&, no_type /*is_iterator*/)
{std::cout << "no iterator: ";}

template < typename T >
inline void show(const char* str) {
show( T(), typename is_iterator(T()) );
std::cout << str << '\n';
}

#define SHOW(T) show<T>(#T)

int main() {
SHOW( int* );
SHOW( int** );
SHOW( int* const* );
SHOW( const int* );
SHOW( const int** );
SHOW( const int* const* );
SHOW( std::vector<int>::const_iterator );
std::cout << '\n';
SHOW( void* );
SHOW( std::vector<int> );
SHOW( int );
}

However said:
Kai-Uwe Bux

Schobi
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,817
Latest member
DicWeils

Latest Threads

Top