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

Overload resolution and templates with nested typedefs

81 views
Skip to first unread message

David Barrett-Lennard

unread,
May 10, 2012, 4:14:33 AM5/10/12
to
Under VS2008, VS2010 I get this behaviour:

template <typename T> struct X { typedef T type; };

void f(double) {}
template <typename T> void f(T) {}

void g(double) {}
template <typename T> void g(typename X<T>::type) {}

void test()
{
f(1); // picks f(T)
g(1); // picks g(double)
}

Is it a bug, or expected? Either way, how can one use enable_if effectively?

David


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

SG

unread,
May 10, 2012, 2:46:04 PM5/10/12
to
On 10 Mai, 10:14, David Barrett-Lennard wrote:
>
> template <typename T> struct X { typedef T type; };
>
> void f(double) {}
> template <typename T> void f(T) {}
>
> void g(double) {}
> template <typename T> void g(typename X<T>::type) {}
>
> void test()
> {
> f(1); // picks f(T)
> g(1); // picks g(double)
> }
>
> Is it a bug, or expected?

This is expected because typename X<T>::type is not considered a
deducible context for the template parameter T. You can't expect the
compiler to test all possible types T so that X<T>::type matches the
argument type. Consider specializations or simply things like this:

template <typename T> struct X { typedef int type; };
^^^
There may be no T or multiple possible Ts.

> how can one use enable_if effectively?

For the return type

template<class T>
typename enable_if<(expr),void>::type
soandso(T);

or since recently (C++2011) much more conveniently as part of an
anonymous and defaulted template parameter:

template<class T, class=typename enable_if<(expr)>::type>
void soandso(T)

which can be shortened using the macro

#REQUIRES(...) ,class=typename std::enable_if<(__VA_ARGS__)>::type

to

template <class T
REQUIRES(expr)
>
void soandso(T);

where 'expr' is supposed to be a compile-time expression convertible
to bool.

Cheers!
SG

Andy Lutomirski

unread,
May 10, 2012, 2:48:25 PM5/10/12
to
On 05/10/2012 01:14 AM, David Barrett-Lennard wrote:
> Under VS2008, VS2010 I get this behaviour:
>
> template <typename T> struct X { typedef T type; };
>
> void f(double) {}
> template <typename T> void f(T) {}
>
> void g(double) {}
> template <typename T> void g(typename X<T>::type) {}
>
> void test()
> {
> f(1); // picks f(T)
> g(1); // picks g(double)
> }
>
> Is it a bug, or expected? Either way, how can one use enable_if effectively?

I'm having trouble finding the relevant part of the standard, but
there's no way this could work -- the type "typename X<T>::type" is, in
general, an arbitrary function of T, and finding a T that matches is at
least NP hard.

To make enable_if work, use it in a context that doesn't require
deduction. For example:

template<typename T>
typename enable_if<whatever condition, return_type>::type G(T);

--Andy

Daniel Krügler

unread,
May 10, 2012, 2:51:25 PM5/10/12
to
On 2012-05-10 10:14, David Barrett-Lennard wrote:
> Under VS2008, VS2010 I get this behaviour:
>
> template<typename T> struct X { typedef T type; };
>
> void f(double) {}
> template<typename T> void f(T) {}
>
> void g(double) {}
> template<typename T> void g(typename X<T>::type) {}
>
> void test()
> {
> f(1); // picks f(T)
> g(1); // picks g(double)
> }
>
> Is it a bug, or expected?

This is expected. Keep in mind that within typename X<T>::type
T is a non-deduced context: It is in general impossible for a compiler
to find a type T given a member type of a template X. In your example
template g does not participate in overload resolution, because
deduction fails, therefore the function g(double) will be selected.

> Either way, how can one use enable_if effectively?

I don't understand you question. enable_if is always used, when
deduction has already taken place, e.g. rewrite g as (I assume that X
represents enable_if here):

template<typename T>
typename enable_if</some_condition on T/, void>::type g(T) {}

or as

template<typename T>
void g(T, typename enable_if</some_condition on T/, void>::type* = 0) {}

Since C++11 new forms are possible taking advantage of default template
arguments of function templates:

template<typename T,
typename = typename enable_if</some_condition on T/, void>::type>
void g(T) {}

or

template<typename T,
typename enable_if</some_condition on T/, void>::type* = 0>
void g(T) {}

HTH & Greetings from Bremen,

Daniel Krügler

Daniel Krügler

unread,
May 10, 2012, 2:54:06 PM5/10/12
to
On 2012-05-10 10:14, David Barrett-Lennard wrote:
> Under VS2008, VS2010 I get this behaviour:
>
> template<typename T> struct X { typedef T type; };
>
> void f(double) {}
> template<typename T> void f(T) {}
>
> void g(double) {}
> template<typename T> void g(typename X<T>::type) {}
>
> void test()
> {
> f(1); // picks f(T)
> g(1); // picks g(double)
> }
>
> Is it a bug, or expected?

This is expected. Keep in mind that within typename X<T>::type T is a
non-deduced context: It is in general impossible for a compiler to
find a type T given a member type of a template X. In your example
template g does not participate in overload resolution, because
deduction fails, therefore the function g(double) will be selected.

> Either way, how can one use enable_if effectively?

I don't understand you question. enable_if is always used, when
deduction has already taken place, e.g. rewrite g as (I assume that X
represents enable_if here):

template<typename T>
typename enable_if</some_condition on T/, void>::type g(T) {}

or as

template<typename T>
void g(T, typename enable_if</some_condition on T/, void>::type* = 0) {}

Since C++11 new forms are possible taking advantage of default
template arguments of function templates:

template<typename T,
typename = typename enable_if</some_condition on T/, void>::type>
void g(T) {}

or

template<typename T,
typename enable_if</some_condition on T/, void>::type* = 0>
void g(T) {}

HTH & Greetings from Bremen,

Daniel Krügler


Johannes Schaub

unread,
May 12, 2012, 2:05:18 PM5/12/12
to
Am 10.05.2012 20:51, schrieb Daniel Krügler:
> On 2012-05-10 10:14, David Barrett-Lennard wrote:
>> Under VS2008, VS2010 I get this behaviour:
>>
>> template<typename T> struct X { typedef T type; };
>>
>> void f(double) {}
>> template<typename T> void f(T) {}
>>
>> void g(double) {}
>> template<typename T> void g(typename X<T>::type) {}
>>
>> void test()
>> {
>> f(1); // picks f(T)
>> g(1); // picks g(double)
>> }
>>
>> Is it a bug, or expected?
>
>> Either way, how can one use enable_if effectively?
>
> I don't understand you question. enable_if is always used, when
> deduction has already taken place, e.g. rewrite g as (I assume that X
> represents enable_if here):
>

> or
>
> template<typename T,
> typename enable_if</some_condition on T/, void>::type* = 0>
> void g(T) {}
>

A integral null pointer constant is not allowed as template argument for
a pointer non-type template parameter. Did this change in a post-C++11
draft? You need to say

= (void*)0

You can say

= nullptr

You can alternatively make it an int

int>::type = 0

As for the benefit over the "typename = typename enable_if ..."
approach, this allows overloading function templates that solely differ
by the SFINAE expression. When using the type template parameter
approach, that's not possible anymore, because the overloads would
solely differ by their default arguments. However, default template
arguments are not part of the signature of a function template, whereas
the template parameter type itself is.

Daniel Krügler

unread,
May 14, 2012, 2:13:12 PM5/14/12
to
On 2012-05-12 20:05, Johannes Schaub wrote:
> Am 10.05.2012 20:51, schrieb Daniel Krügler:
>> On 2012-05-10 10:14, David Barrett-Lennard wrote:
>>> Under VS2008, VS2010 I get this behaviour:
>>>
>>> template<typename T> struct X { typedef T type; };
>>>
>>> void f(double) {}
>>> template<typename T> void f(T) {}
>>>
>>> void g(double) {}
>>> template<typename T> void g(typename X<T>::type) {}
>>>
>>> void test()
>>> {
>>> f(1); // picks f(T)
>>> g(1); // picks g(double)
>>> }
>>>
>>> Is it a bug, or expected?
>>
>>> Either way, how can one use enable_if effectively?
>>
>> I don't understand you question. enable_if is always used, when
>> deduction has already taken place, e.g. rewrite g as (I assume that X
>> represents enable_if here):
>>
>
>> or
>>
>> template<typename T,
>> typename enable_if</some_condition on T/, void>::type* = 0>
>> void g(T) {}
>>
>
> A integral null pointer constant is not allowed as template argument for
> a pointer non-type template parameter. Did this change in a post-C++11
> draft?

You are right, I was a bit hasty here.

> You need to say
>
> = (void*)0
>
> You can say
>
> = nullptr
>
> You can alternatively make it an int
>
> int>::type = 0

Yes, this is what I also usually use.

> As for the benefit over the "typename = typename enable_if ..."
> approach, this allows overloading function templates that solely differ
> by the SFINAE expression. When using the type template parameter
> approach, that's not possible anymore, because the overloads would
> solely differ by their default arguments. However, default template
> arguments are not part of the signature of a function template, whereas
> the template parameter type itself is.

Correct.

Greetings from Bremen,

Daniel Krügler


0 new messages