views:

77

answers:

1

I have an SFINAE test for checking if an class has a function. The test works correctly, but I get compiler errors when I try to use it in an if statement.

//SFINAE test for setInstanceKey()
template <typename K>
class HasSetInstanceKey
{
    template <typename C>
    static char test( typeof(&C::setInstanceKey) );

    template <typename C>
    static long test(...);

public:
    enum { value = 1 == sizeof(test<K>(0)) };
};

I get "error: ‘class Node’ has no member named ‘setInstanceKey’" on the second line even though the else clause should be executing.

if ( 0 != HasSetInstanceKey<T>::value)
    instance->setInstanceKey(instanceKey);
else
    ...

Is there a way to make this work?

Thanks.

+3  A: 

Just because the if-branch is never entered doesn't mean the code within the branch can be invalid. (Another way to think about it: you aren't guaranteed anything about optimizations, yet your code would only be valid with a dead-branch optimization.)

What you do is shift the branch to a function. Typically you have a framework like this:

// holds some integral constant
template <typename T, T V>
struct integral_constant
{
    static const T value = V;
};

// holds a boolean constant
template <bool V>
struct bool_type : integral_constant<bool, V>
{};

typedef bool_type<true> true_type; // a true boolean constant
typedef bool_type<false> false_type; // a false boolean constant

typedef const true_type& true_tag; // tag a function as the true variant
typedef const false_type& false_tag; // tag a function as the false variant

Then something like this:

namespace detail
{
    template <typename T, typename KeyType>
    void foo(T* instance, const KeyType& instanceKey, true_tag)
    {
        // we are in the true variant, so our meta-function's value was true
        // therefore, instance has the ability to do setInstanceKey
        instance->setInstanceKey(instanceKey);
    }

    template <typename T, typename KeyType>
    void foo(T*, const KeyType&, false_tag)
    {
        // we are in the false variant, so our meta-function's value was false
        // therefore, instance does not have the right capabilities, 
        // so do nothing
    }
}

// interface, forwards to correct implementation function
template <typename T, typename KeyType>
void foo(T* instance, const KeyType& instanceKey)
{
    // pass instance, but to the overloaded foo 
    // that accepts the right boolean result
    detail::foo(instance, instanceKey, // plug the value into a bool_type, 
                bool_type<HasSetInstanceKey<T>::value>()); // and instantiate it
                // will either go into the true_tag or false_tag
}

It's good practice to have to meta-functions inherit from the correct bool_type, to ease use:

namespace detail
{
    // implementation
    template <typename K>
    class HasSetInstanceKey
    {
        // note, using char and long doesn't necessarily guarantee
        // they each have a unique size. do this instead:
        typedef char yes[1];
        typedef char no[2]; // these must have different sizes

        template <typename C>
        static yes& test( typeof(&C::setInstanceKey) );

        template <typename C>
        static no& test(...);

    public:
        // check against size of yes result
        static const bool value = sizeof(test<K>(0)) == sizeof(yes);
    };
}

template <typename K>
struct HasSetInstanceKey : // delegate to implementation, take result and
    bool_type<detail::HasSetInstanceKey<K>::value> // inherit from the 
                                                   // appropriate bool_type
{};

So it just becomes:

template <typename T, typename KeyType>
void foo(T* instance, const KeyType& instanceKey)
{
    // because it inherits from bool_type, it can be implicitly
    // converted into either true_tag or false_tag
    detail::foo(instance, instanceKey, HasSetInstanceKey<T>());
}
GMan
Kevin
@Kevin: Unless `true_tag` is the same type as `false_tag`, that's not possible. Have you mis-typed them to be the same thing?
GMan
@GMan: adding a "template <typename T>" on the line above each foo() fixes the "previously defined" error. not sure if i should expect this to work when i test it though... still debugging other errors for now.
Kevin
@Kevin: Oh, I'm sorry! That's completely my fault, trivial error. The functions need to be a template to get a `T`! I've updated the code with comments, matched it to your usage better, and gave you some tips about implementing meta-functions.
GMan
@GMan: thanks. there was an argument on another SO thread about whether to use an enum or a static variable for value. didn't look like it was really resolved. is there a reason you switched?
Kevin
@Kevin: I just think it looks better because it's consistent and easier to read. I know it's mostly personal preference, you can switch it back of course. (There are arguments like "static const" forces the compiler to store that value somewhere in the executable, but compilers know to strip such things just like it would an enum anyway. And if not, it doesn't make a difference anyway.)
GMan