6

I'm learning how to use SFINAE to my advantage. I'm trying to use it to select the function implementation based on existence of a serialize() function in an object.

This is the code I use to determine, if the type defines the serialize() function:

template <typename T>
class HasSerialize {
    private:
        typedef char yes[1];
        typedef char no[2];

        template <typename C> static yes& test(char[sizeof(&C::serialize)]) ;
        template <typename C> static no& test(...);
    public:
        static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

However, it seems to give exactly oposite results on GCC and on Clang. Assume the following code:

template<bool T>
class NVPtypeSerializer {
    public:
        template<typename C>
        static xmlChar* serialize(C value) {
            // serize() is not available
        }
};

template<>
struct NVPtypeSerializer<true> {
    public:
        template<typename T>
        static xmlChar* serialize(T value) {
            return value.serialize();
        }
};

Which is called like this:

foo = NVPtypeSerializer<HasSerialize<Bar>::value >::serialize(value);

Where the class Bar doesn't have the serialize() function. This code compiles fine under Clang 3.1, however on GCC 4.7.1 I get the following errors:

error: ‘class Bar’ has no member named ‘serialize’

If I change the struct NVPtypeSerializer<true> to struct NVPtypeSerializer<false> it can be compiled on GCC, but Clang gives the following error:

error: no member named 'serialize' in 'Bar'

Where is the problem? Is it in my code? I'd like to have the code portable as much as possible.

stativ
  • 1,452
  • 12
  • 23
  • This indeed is quite surprising, especially given that the name `Bar` is not appearing anywhere. – 6502 Jul 19 '12 at 18:02
  • Oh, sorry, I wrote ``NVPtypeSerializer >``, but T is a type name, which is Bar at some point of compilation. I changed it in the original question. – stativ Jul 19 '12 at 18:15
  • So, I found the solution to the compilation error: ``template struct type_check; template static yes& test(type_check);`` However I still don't understand why the previous solution behaves in a such strange way. – stativ Jul 19 '12 at 19:18
  • I've been there, I've done that... but luckily I'm not into extreme template metaprogramming any more and you really are probably at the moment unable to understand how funny (and how sad) is at the same time to see such a waste of mental efforts. I hope you'll be able to get out of this sickness as soon as possible, investing your time in something that will make you more positively productive. Unfortunately my experience says there is a quite high probability you will just classify me as a C++ bashing troll. – 6502 Jul 20 '12 at 05:35
  • [similar question](http://stackoverflow.com/questions/12813351/sfinae-with-decltype-bug-in-clang-or-gcc/12815815#12815815) with good answer – Leonid Volnitsky Oct 11 '12 at 16:33
  • Leonid: thank you for pointing out that question. However it seems to be a bit different issue – if I make the parameter C of the test() function dependent on T by using the default template argument as suggested, the problem remains. But I may not understood the solution correctly. – stativ Oct 13 '12 at 15:11

1 Answers1

3

Is this really the code test(char[sizeof(&C::serialize)])? Note that a declaration of a function that takes an array actually declares a function that takes a pointer:

template <typename C> static yes& test(char[sizeof(&C::serialize)]) ;

That actually means:

template <typename C> static yes& test( char* );

Which incidentally is what makes your call test<C>(0) compile. I don't think that is the proper way of detecting whether the function exists or not. Google on how to detect whether a member/member function exists in a class using SFINAE.

(A simple solution would be adding an extra defaulted argument --provided that you have a C++11 enabled compiler:

template <typename C, std::size_t = sizeof(&C::serialize)> 
static yes& test(int) ;

)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I don't see why it should be invalid. Even though it will decay to `char*`, `&C::serialize` should still have to be evaluated, so this seems like a valid SFINAE use. Maybe GCC is [over-optimizing its AST](http://stackoverflow.com/a/11530632/636019) again? In any case, +1 for your workaround -- I can't wait until VC++ gets default function template arguments. – ildjarn Jul 19 '12 at 18:19
  • @ildjarn: When the compiler parses the declaration of the function an *array of N elements of type T* is transformed to *pointer to T*, so the overload does not mention `sizeof(&C::serialize)` at all. I might be wrong, though. – David Rodríguez - dribeas Jul 19 '12 at 18:22
  • I got this code from the question [Is it possible to write a C++ template to check for a function's existence?](http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence). MSalters suggests to use this, because it doesn't depend on compiler specific extensions such as typeof, nor it requires C++11 like decltype does. – stativ Jul 19 '12 at 18:22
  • @David : I'm not positive either, but I think `char[0]` would be ill-formed, so I think it must evaluate the size _before_ decaying into a pointer. – ildjarn Jul 19 '12 at 18:26
  • @ildjarn: `void f( char[0] )` compiles fine in both gcc and clang++ (at least in my old versions: gcc 4.5.3/clang++ 3.0), but `void f( char[-1] )` fails in both, so it could be either way. – David Rodríguez - dribeas Jul 19 '12 at 18:50
  • I tried googling even more and they often use typeof, decltype. I also found some code that uses SFINAE two times, but it seems much more complicated. – stativ Jul 19 '12 at 19:02
  • @stativ: The [answer by litb](http://stackoverflow.com/a/264088/36565) to the question you linked does not use either. – David Rodríguez - dribeas Jul 19 '12 at 19:22
  • @David: I found out how to use that moments ago (see my comment under the original question). However I'm still puzzled by the unexpected results when using ``char[sizeof()]``. – stativ Jul 19 '12 at 19:25
  • Given that std says following about array size: "its value shall be greater than zero." while it doesn't seem to say anything whether it applies to a function taking an array argument, I'm pretty surprised that char[0] works, but char[-1] doesn't. I'd expect consistent behavior: none of them works - the array size is determined before replacing array with a pointer, both works - the array size is not checked. – stativ Jul 19 '12 at 19:36
  • @ildjarn char[0] is legal since c99 http://stackoverflow.com/questions/14643406/whats-the-need-of-array-with-zero-elements , and I used to mimic linklist with it. – Feng Wang Oct 21 '15 at 14:08
  • @FengWang : It is still explicitly illegal in all _C++_ standards; Clang even warns that it only compiles as an _extension_. – ildjarn Oct 22 '15 at 02:14