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

So, it's not just ANY compile error that can be used with SFINAE.

10 views
Skip to first unread message

Andy Venikov

unread,
May 12, 2010, 4:07:16 AM5/12/10
to
To do type introspection most of the time we use compile errors about non-existing types combined with SFINAE.

But I thought that any compile error during an instantiation of a class template would disable that overload from the list of viable instantiations suppressing the actual error.

Turns out not to be the case.

For example, try implementing has_foo<> by taking an address of foo. If foo exists, everything fine. If foo doesn't exist, then you'll get compile-time error no matter what.

Code:

template <bool Test, typename T = void>
struct enable_if;

template <typename T>
struct enable_if<true, T>
{
typedef T type;
static const bool value = true;
};

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T>
struct take_address_of_foo
{
static const size_t nTest = sizeof(&T::foo);
static const bool value = enable_if<nTest>::value;
};

template <typename T>
struct has_foo<T, typename enable_if<take_address_of_foo<T>::value>::type>
{
static const bool value = true;
};


struct WithFoo
{
int foo(int, int, int);
};

struct NoFoo
{
};


bool b1 = has_foo<WithFoo>::value; //compiles fine, produces true
bool b2 = has_foo<NoFoo>::value; //compile-time error
bool b3 = has_foo<int>::value; //compile-time error

What kinds of error can and can't be used with SFINAE?

Thanks,
Andy.

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

Johannes Schaub (litb)

unread,
May 12, 2010, 6:32:30 PM5/12/10
to
Andy Venikov wrote:

If a class is instantiated, it's too late for SFINAE to catch errors. SFINAE works at deduction time. Things that immediately have to do with argument deduction and substitution of the deduced arguments into function parameter lists are detected. Errors
that result from the generation of functions or classes, even if the corresponding entities were created because of a deduction going on somewhere, won't be detected at all, neither will errors that result from access violations or from implicit
definitions. For example if in a SFINAE context you do "sometype()" and that causes the default constructor of "sometype" to be defined and for some reason the definition is ill-formed (for example if "sometype" has a const datamember or reference
member), it's a hard error and not a SFINAE error.

The FCD says:

"[ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-
defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]"

In your case, "take_address_of_foo<NoFoo>" is instantiated, and the error, namely "sizeof(&NoFoo::foo)", has nothing to do with deduction of the template arguments of the partial specialization of has_foo. I imagine it by thinking of the instantiated
class as being self-contained in itself, and not dependent on how it was brought into being.

Andy Venikov

unread,
May 13, 2010, 7:37:56 PM5/13/10
to
Johannes Schaub (litb) wrote:
<snip>

> If a class is instantiated, it's too late for SFINAE to catch errors. SFINAE works at deduction time. Things that immediately have to do with argument deduction and substitution of the deduced arguments into function parameter lists are detected. Errors that result from the generation of functions or classes, even if the corresponding entities were created because of a deduction going on somewhere, won't be detected at all, neither will errors that result from access violations or from implicit definitions. For example if in a SFINAE context you do "sometype()" and that causes the default constructor of "sometype" to be defined and for some reason the definition is ill-formed (for example if "sometype" has a const datamember or reference member), it's a hard error and not a SFINAE error.

Hmm, still don't get it.
According to your description, enable_if wouldn't work either. When we say eanble_if<false, type>::type we DO instantiate enable_if, and according to your description, it's too late and we should get a hard error about non-existing typedef type of
enable_if<false, type>

In my example, I'm doing the (almost) the same thing as enable_if: we try to instantiate take_address_of_foo<NoFoo>::value (as opposed to trying to instantiate enable_if<false, type>::type) and the instantiation fails do to an error. Overload is not
selected. The only thing that's different is the type of the error.


Thanks,
Andy.

Johannes Schaub (litb)

unread,
May 14, 2010, 11:51:46 AM5/14/10
to
Andy Venikov wrote:

> Johannes Schaub (litb) wrote:
> <snip>
>> If a class is instantiated, it's too late for SFINAE to catch errors.
>> SFINAE works at deduction time. Things that immediately have to do with
>> argument deduction and substitution of the deduced arguments into
>> function parameter lists are detected. Errors that result from the
>> generation of functions or classes, even if the corresponding entities
>> were created because of a deduction going on somewhere, won't be detected
>> at all, neither will errors that result from access violations or from
>> implicit definitions. For example if in a SFINAE context you do
>> "sometype()" and that causes the default constructor of "sometype" to be
>> defined and for some reason the definition is ill-formed (for example if
>> "sometype" has a const datamember or reference member), it's a hard error
>> and not a SFINAE error.
>
> Hmm, still don't get it.
> According to your description, enable_if wouldn't work either. When we say
> eanble_if<false, type>::type we DO instantiate enable_if, and according to
> your description, it's too late and we should get a hard error about
> non-existing typedef type of enable_if<false, type>
>

No. instantiation of "enable_if<false, type>" doesn't fail. It's the access
to "::type", which happens during deduction at the very toplevel, which
fails.

> In my example, I'm doing the (almost) the same thing as enable_if: we try
> to instantiate take_address_of_foo<NoFoo>::value (as opposed to trying to
> instantiate enable_if<false, type>::type) and the instantiation fails do
> to an error. Overload is not selected. The only thing that's different is
> the type of the error.
>

Nope see above :)

DeMarcus

unread,
May 14, 2010, 12:00:56 PM5/14/10
to

Could you solve it using lazy_enable_if? See Section 3.3.

http://www.boost.org/doc/libs/1_43_0/libs/utility/enable_if.html

Andy Venikov

unread,
May 20, 2010, 11:33:46 PM5/20/10
to
Johannes Schaub (litb) wrote:
<snip>
>>
>
> No. instantiation of "enable_if<false, type>" doesn't fail. It's the access
> to "::type", which happens during deduction at the very toplevel, which
> fails.

Still not clear.

The way I understand it now, is that if the error happens while trying
to instantiate a class during template argument deduction, then SFINAE
kicks in. As in if the statement that generates an error is not in the
body of the class but in <...> of the template specialization, then it's
OK.

I re-organized my original example with this in mind. And I got some
surprising results.
It compiles on both gcc 4.4 and on Comeau online. But I get different
result. Gcc is able to tell the difference and has_foo<...>::value is
true only for the types that have function foo. Comeau always returns
false. Now I wander what compiler is right?

Here's the re-worked example:
(the idea was to move sizeof(&T::foo) from within class body to class
declaration>


-----> Code start
typedef unsigned int size_t;

template <bool Test, typename T = void>
struct enable_if;

template <typename T>
struct enable_if<true, T>
{
typedef T type;
static const bool value = true;
};

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T, typename Enable = void>

struct take_address_of_foo


{
static const bool value = false;
};

template <typename T>
struct take_address_of_foo<T,
typename enable_if<0 < sizeof(decltype(&T::foo))>::type>


{
static const bool value = true;
};

template <typename T>


struct has_foo<T, typename enable_if<take_address_of_foo<T>::value>::type>
{
static const bool value = true;
};


struct WithFoo
{
int foo(int, int, int);
};

struct NoFoo
{
};


template <typename T, bool HasFoo = T::value>
struct CompileError
{
typedef typename T::non_existing_type_brrr type;
};

//All lines compile fine. But Comeau alway returns false.
//Gcc returns true for the "WithFoo" case
const bool B1 = has_foo<WithFoo>::value;
const bool B2 = has_foo<NoFoo>::value;
const bool B3 = has_foo<int>::value;

<-------- Code end


Thanks,
Andy.

Andy Venikov

unread,
May 25, 2010, 2:56:00 PM5/25/10
to
Andy Venikov wrote:
<snip>

Sorry to re-post my question, but I think the question got lost in
between the lines.

The following code compiles fine on both gcc 4.3 and Comeau online (with
and without C++0x support) but produces different results. Gcc returns
what I'd expect (true if T has foo and false if it doesn't) Comeau
always returns false.

I wonder, who's right?

typedef unsigned int size_t;

template <bool Test, typename T = void>
struct enable_if;

template <typename T>
struct enable_if<true, T>
{
typedef T type;
static const bool value = true;
};

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T, typename Enable = void>
struct take_address_of_foo
{
static const bool value = false;
};

template <typename T>
struct take_address_of_foo<T,

typename enable_if<0 < sizeof(&T::foo)>::type>


{
static const bool value = true;
};

template <typename T>
struct has_foo<T, typename enable_if<take_address_of_foo<T>::value>::type>
{
static const bool value = true;
};


struct WithFoo
{
int foo(int, int, int);
};

struct NoFoo
{
};


template <typename T, bool HasFoo = T::value>
struct CompileError
{
typedef typename T::non_existing_type_brrr type;
};

//Force compile-error to show the results of has_foo<...>::type
//Comeau alway returns false.


//Gcc returns true for the "WithFoo" case

typedef CompileError<WithFoo, has_foo<WithFoo>::value>::type T1;
typedef CompileError<NoFoo, has_foo<NoFoo>::value>::type T2;
typedef CompileError<int, has_foo<int>::value>::type T3;

Mathias Gaunard

unread,
May 27, 2010, 4:35:39 AM5/27/10
to
On 12 mai, 09:07, Andy Venikov <swojchelo...@gmail.com> wrote:
>
> For example, try implementing has_foo<> by taking an address of foo. If foo exists, everything fine. If foo doesn't exist, then you'll get compile-time error no matter what.

It is well-known how to do this, consult the relevant articles.
In C++0x, you can do even better though, you can test any expression,
which allows to take into account conversions and object constness
automatically.


>
> Code:
>
> template <bool Test, typename T = void>
> struct enable_if;
>
> template <typename T>
> struct enable_if<true, T>
> {
> typedef T type;
> static const bool value = true;
>
> };
>
> template <typename T, typename Enable = void>
> struct has_foo
> {
> static const bool value = false;
>
> };
>
> template <typename T>
> struct take_address_of_foo
> {
> static const size_t nTest = sizeof(&T::foo);
> static const bool value = enable_if<nTest>::value;
>
> };
>
> template <typename T>
> struct has_foo<T, typename enable_if<take_address_of_foo<T>::value>::type>
> {
> static const bool value = true;
>
> };

You're doing it wrong, this doesn't test for T::foo in an SFINAE
context.

Here is an example of how to do it.

template<typename T>
struct has_foo

{
private:
struct found {};
struct not_found { char two[2]; };

template<typename X, int (X::*)(int, int, int)>
struct member {};

template<typename X>
static found test(member<X, &X::foo>*);

template<typename X>
static not_found test(...);

public:
static const bool value = sizeof(test<T>(0)) == sizeof(found);
typedef boost::mpl::bool_<value> type;
};

Ilya Sokolov

unread,
May 27, 2010, 2:35:02 PM5/27/10
to
Mathias Gaunard wrote:
> On 12 mai, 09:07, Andy Venikov <swojchelo...@gmail.com> wrote:
>> For example, try implementing has_foo<> by taking an address of foo.
>> If foo exists, everything fine. If foo doesn't exist, then you'll
>> get compile-time error no matter what.
>
> It is well-known how to do this, consult the relevant articles.
> In C++0x, you can do even better though, you can test any expression,
> which allows to take into account conversions and object constness
> automatically.

"Solving the SFINAE problem for expressions"
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html

> [snip]


>
> template<typename T>
> struct has_foo
> {
> private:
> struct found {};
> struct not_found { char two[2]; };
>
> template<typename X, int (X::*)(int, int, int)>
> struct member {};
>
> template<typename X>
> static found test(member<X, &X::foo>*);
>
> template<typename X>
> static not_found test(...);
>
> public:
> static const bool value = sizeof(test<T>(0)) == sizeof(found);
> typedef boost::mpl::bool_<value> type;
> };

Is it required by the standard that sizeof(found) != sizeof(not_found)?

Öö Tiib

unread,
May 27, 2010, 2:35:13 PM5/27/10
to


I think that it is undefined behavior in has_foo<WithFoo>::value.
[5.3.3] talks about every sort of types but fails to talk about
pointers to member functions and their overloads. Closest thing is:

[5.3.3/3] "The sizeof operator can be applied to a pointer to a
function, but shall not be applied directly to a function."

So Comeau probably thinks that pointers to member functions are
illegal for sizeof (always false) and gcc thinks that these are legal
(what you expect). Can not blame them because compiler has to do
something when situation is undefined.

Ilya Sokolov

unread,
May 27, 2010, 2:35:34 PM5/27/10
to
Andy Venikov wrote:
> Andy Venikov wrote:
> <snip>
>
> Sorry to re-post my question, but I think the question got lost in
> between the lines.
>
> The following code compiles fine on both gcc 4.3 and Comeau online (with
> and without C++0x support) but produces different results. Gcc returns
> what I'd expect (true if T has foo and false if it doesn't) Comeau
> always returns false.
>
> I wonder, who's right?

I don't see anything in the standard that prevents Comeau from matching
partial specialization of task_address_of_foo.

> [snip]

Shorter variant would be:

template <bool Test, typename T = void>
struct enable_if;

template <typename T>
struct enable_if<true, T>
{
typedef T type;
static const bool value = true;
};

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T>
struct has_foo<T, typename enable_if<sizeof(&T::foo)>::type>


{
static const bool value = true;
};

or:

template<int>
struct void_
{
typedef void type;
};

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T>
struct has_foo<T, typename void_<sizeof(&T::foo)>::type>


{
static const bool value = true;
};

or using cpp0x:

template <typename T, typename Enable = void>
struct has_foo
{
static const bool value = false;
};

template <typename T>
struct has_foo<T, decltype(T::foo)>
{


static const bool value = true;
};

Sadly, for all variants has_foo<WithFoo>::value == false
in como.

Andy Venikov

unread,
May 28, 2010, 4:05:12 AM5/28/10
to
Mathias Gaunard wrote:
<snip>

> It is well-known how to do this, consult the relevant articles.
> In C++0x, you can do even better though, you can test any expression,
> which allows to take into account conversions and object constness
> automatically.

Oh, I've read articles on this topic. I was just wondering what contexts
can SFINAE be used in and where it can't and tried to come up with an
example to illustrate that.

<snip>

>
> You're doing it wrong, this doesn't test for T::foo in an SFINAE
> context.
>
> Here is an example of how to do it.
>
> template<typename T>
> struct has_foo
> {
> private:
> struct found {};
> struct not_found { char two[2]; };
>
> template<typename X, int (X::*)(int, int, int)>
> struct member {};
>
> template<typename X>
> static found test(member<X, &X::foo>*);
>
> template<typename X>
> static not_found test(...);
>
> public:
> static const bool value = sizeof(test<T>(0)) == sizeof(found);
> typedef boost::mpl::bool_<value> type;
> };

Actually, a minimalistic approach would be this:

template<typename T, typename U = void>
struct has_member
{ static const bool result = false; };

template<typename T>
struct has_member<T, typename enable_if<sizeof(&T::member)>::type>
{ static const bool result = true; };


But, as several people noticed before, for some reason it always
produces "false" in comeau. Althought it works as expected in gcc and
VC++. (Elsewhere in this thread I tried to solicit an answer to a
question who's right).

A very beautiful approach (the one that works great for member templates
too as opposed to the approach you've given above) can be found here:

http://www.rsdn.ru/forum/cpp/2720363.aspx
(unfortunately it's in Russian, but the technique is made obvious by
looking at the code). It relies on the ambiguity rule while trying to do
name lookup as described in 10.2.2

Andy.

Andy Venikov

unread,
May 28, 2010, 4:07:26 AM5/28/10
to
�� Tiib wrote:
<snip>


> I think that it is undefined behavior in has_foo<WithFoo>::value.
> [5.3.3] talks about every sort of types but fails to talk about
> pointers to member functions and their overloads. Closest thing is:
>
> [5.3.3/3] "The sizeof operator can be applied to a pointer to a
> function, but shall not be applied directly to a function."
>
> So Comeau probably thinks that pointers to member functions are
> illegal for sizeof (always false) and gcc thinks that these are legal
> (what you expect). Can not blame them because compiler has to do
> something when situation is undefined.

Hmm, interesting point.

Although typing this quick code in comeau:

struct A
{
void foo() {}
};

unsigned int n = sizeof(&A::foo);


compiled just fine...

(and by the way, the result of sizeof(&A::foo) is 8 -)

Thanks,
Andy.

0 new messages