Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Trying to apply SFINAE

0 views
Skip to first unread message

Hendrik Schober

unread,
Sep 29, 2008, 12:20:34 PM9/29/08
to
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

Victor Bazarov

unread,
Sep 29, 2008, 1:39:53 PM9/29/08
to
Hendrik Schober wrote:
> [..]

> #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.
> [..]

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);
}

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

James Kanze

unread,
Sep 30, 2008, 4:51:46 AM9/30/08
to
On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.de> wrote:

> 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< typename T >
void testHelper( T, typename std::iterator_traits< T
>::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.

--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Hendrik Schober

unread,
Sep 30, 2008, 5:20:55 AM9/30/08
to
Victor Bazarov wrote:
> 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.

> V

Schobi

Hendrik Schober

unread,
Sep 30, 2008, 5:58:25 AM9/30/08
to
James Kanze wrote:
> On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.de> wrote:
>
>> 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.

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

Francesco

unread,
Sep 30, 2008, 8:51:40 AM9/30/08
to
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:

Victor Bazarov

unread,
Sep 30, 2008, 10:50:03 AM9/30/08
to

They are probably correct :-/

Let's try to involve a class where you can make a partial specialisation:

template<class T, bool> struct ActualWorker; // "abstract"
template<class T> struct ActualWorker<T,false> // non-iterator
{
static void test(T /*a1*/, T /*a2*/) { /*whatever*/ }
};

template<class T> class ActualWorker<T,true> // iterator
{
static void test(T /*i1*/, T /*i2*/) { /*whatever*/ }
};

// now - how do we determine it's an iterator?
template<class T> struct IsIterator { enum { yes = 0 }; };
template<class T> struct IsIterator {
... // here you need to add some way to set 'yes' to 1
... // if 'T' is an iterator. It's up to you to define
... // what is an iterator and what isn't.
};

template<class T> void test(T t1, T t2) {
return ActualWorker<T, IsIterator<T>::yes >::test(t1, t2);
}

int main()
{
test(42, 666);
int foo[] = { 1,2,3,4 };
test(foo, foo+4);

James Kanze

unread,
Oct 1, 2008, 4:15:42 AM10/1/08
to
On Sep 30, 11:58 am, Hendrik Schober <spamt...@gmx.de> wrote:
> James Kanze wrote:
> > On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.de> wrote:

> >> 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.

> 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.

> Is it?

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.

Hendrik Schober

unread,
Oct 1, 2008, 3:25:19 PM10/1/08
to
Victor Bazarov wrote:
> 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!

> V

Schobi

Hendrik Schober

unread,
Oct 1, 2008, 3:39:39 PM10/1/08
to
James Kanze wrote:
> On Sep 30, 11:58 am, Hendrik Schober <spamt...@gmx.de> wrote:
>> James Kanze wrote:
>>> On Sep 29, 6:20 pm, Hendrik Schober <spamt...@gmx.de> wrote:
> [...]

>>>> which I need to call. (In reality, these are constructors,
>>>> in case that matters.)
>
>>> It could be critical, if you need the initialization list.
>
>> 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.)

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.
>
>> Is it?
>
> 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

Hendrik Schober

unread,
Oct 1, 2008, 3:47:12 PM10/1/08
to
Francesco wrote:
> 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

Kai-Uwe Bux

unread,
Oct 4, 2008, 10:37:53 PM10/4/08
to
Francesco wrote:


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

Hendrik Schober

unread,
Oct 6, 2008, 6:00:16 AM10/6/08
to
Kai-Uwe Bux wrote:
> 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, this still needs a forwarding function... <sighs>

> Kai-Uwe Bux

Schobi

0 new messages