[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

Trying to apply SFINAE

Hendrik Schober

9/29/2008 4:21:00 PM

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
12 Answers

Victor Bazarov

9/29/2008 5:40:00 PM

0

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

9/30/2008 8:52:00 AM

0

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

9/30/2008 9:21:00 AM

0

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

9/30/2008 9:58:00 AM

0

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

9/30/2008 12:52:00 PM

0

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

Victor Bazarov

9/30/2008 2:50:00 PM

0

Hendrik Schober wrote:
> 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.

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

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

10/1/2008 8:16:00 AM

0

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.

--
James Kanze (GABI Software) email:james.kanze@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

10/1/2008 7:25:00 PM

0

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

10/1/2008 7:40:00 PM

0

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

10/1/2008 7:47:00 PM

0

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