Могу ли я использовать тест SFINAE в операторе потока управления? - PullRequest
2 голосов
/ 13 августа 2010

У меня есть тест SFINAE для проверки, есть ли у класса функция. Тест работает правильно, но я получаю ошибки компилятора, когда пытаюсь использовать его в операторе if.

//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)) };
};

Я получаю сообщение об ошибке «У узла класса нет члена с именем setInstanceKey» во второй строке, хотя должно выполняться условие else.

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

Есть ли способ заставить эту работу?

Спасибо.

1 Ответ

3 голосов
/ 13 августа 2010

Тот факт, что ветвь if никогда не вводится, не означает, что код внутри ветки может быть недействительным. (Еще один способ подумать об этом: вам ничего не гарантировано в отношении оптимизации, но ваш код будет действителен только при оптимизации в мертвой ветке.)

Что вы делаете, это сдвигаете ветку на функцию. Обычно у вас есть такой фреймворк:

// 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

Тогда как то так:

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
}

Хорошей практикой является использование мета-функций, наследуемых от правильных bool_type, чтобы упростить использование:

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
{};

Так оно просто становится:

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>());
}
...