views:

110

answers:

2

I'm trying to use SFINAE to distinguish a class that has a member called 'name'. I set things up in what seems to be the standard pattern but it's not working -- instead of silently ignoring the 'failed' substitution, the compiler produces an error.

I'm sure I've run up against some template substitution rule, I'd be grateful if someone could explain which one.

This is a stripped down example. I'm using gcc:

 template <typename U> string test( char(*)[sizeof(U::name)] = 0 ) { return "has name!"; }
 template <typename U> string test(...) { return "no name"; }

 struct HasName { string name; }
 struct NoName  {}

 cout << "HasName: " << test<HasName>(0) << endl;  //fine
 cout << "NoName: " << test<NoName>(0) << endl;    //compiler errors:

 //error: size of array has non-integral type `<type error>'
 //error: `name' is not a member of `NoName'
+1  A: 

The following appears valid (although as Michael says, it doesn't necessarily give the result you want on other compilers):

#include <string>
#include <iostream>
using namespace std;

template <typename U> string test( char(*)[sizeof(U::name)] = 0 ) { return "has name!"; }
template <typename U> string test(...) { return "no name"; }

struct HasName { static string name; };
struct NoName  { };

 int main() {
    cout << "HasName: " << test<HasName>(0) << endl;
    cout << "NoName: " << test<NoName>(0) << endl;
}

Output:

HasName: has name!
NoName: no name

gcc (GCC) 4.3.4 20090804 (release) 1

Comeau also accepts the code.

Steve Jessop
Some additional data points: MSVC 9/10 also accepts the code (even without the `static` on the `name` member), but doesn't produce the expected output. MinGW (GCC 3.4.5) doesn't compile it either with or without the member being `static`.
Michael Burr
@Michael: then perhaps I should say "appears fine". I usually take "Comeau accepts it" as synonymous with "it's valid C++", but I recognise this is merely a very good approximation :-)
Steve Jessop
Oh, and while Comeau accepts the code, it produces the same output as MSVC (that is, it says "no name" for both calls to `test<>()`).
Michael Burr
@Michael: also, I have a vague feeling that taking the `sizeof` non-static data member using the `class::member` syntax is allowed in C++0x. So not too surprising if compilers accept it without the `static`.
Steve Jessop
@Steve g++ accepts it without the `static` using `-std=c++98 -pedantic`. Not sure if g++ is wrong here or what.
Tyler McHenry
A bit hard to post a compilable test case when the whole point of my question is that it does *not* compile. (and not because of my careless omission of semicolons when retyping the code on SO. Gee, talk about nit-picky!) I am using gcc but a rather old version I fear (I'm not at work now and thus can't tell you the exact version but I'm pretty sure it's 3.7 or thereabouts), and that is probably the source of my trouble; it's good to know that's the cause rather than a fundamental misunderstanding of SFINAE; now I just need to get my company to upgrade its compiler.
c-urchin
If it weren't for the snarky 'consider posting...' I would mark this answer as accepted.
c-urchin
@Tyler: thanks for that data point. I'd like to believe that g++ is correct.
c-urchin
Well, the snark was just that your posted code didn't issue the errors you said it did (not on my version of the compiler, and I suspected not on others). I'm still not sure anyone's got to the bottom of it, though - if Comeau and MSVC give one result and GCC gives another, it may still be that the code is relying on behaviour that isn't fully defined, or even a GCC bug. So I don't know that *I'd* accept my answer :-)
Steve Jessop
@Tyler: odd, g++ doesn't accept it without `static` for me. I thought it was one of those annoying missing things C++0x fixes. Not sure how much flipping through the standard it would take to prove it one way or the other, though - if I'm right then it's because the standard *doesn't* say you *can* do it...
Steve Jessop
A: 

Here's an attempt at this:

// Tested on Microsoft (R) C/C++ Optimizing Compiler Version 15.00.30729.01
template<typename T>
class TypeHasName
{
private:
    typedef char (&YesType)[2];
    typedef char (&NoType)[1];

    struct Base { int name; };
    struct Derived : T, Base { Derived(); };

    template<typename U, U> struct Dummy;

    template<typename U>
    static YesType Test(...);

    template<typename U>
    static NoType Test(Dummy<int Base::*, &U::name>*);

public:
    enum { Value = (sizeof(Test<Derived>(0)) == sizeof(YesType)) };
};

#include <string>  
#include <iostream>  

struct HasName { std::string name; };  
struct NoName {};

int main()
{  
    std::cout << "HasName: " << TypeHasName<HasName>::Value << std::endl;  
    std::cout << "NoName: " << TypeHasName<NoName>::Value << std::endl;  
    return 0;
}

The idea is that if T has a variable named name, then Derived will have two name variables (one from T and one from Base). If T does not declare a name variable, then Derived will only have one from Base.

If Derived has two name variables, then the expression &U::name in the second Test() overload will be ambiguous and SFINAE should remove that function from the overload set.

In silico