lundi 20 avril 2015

Why doesn't SFINAE work for a templated class' function but works for a templated function and templated class function?

In this example, I'm executing a function based on if that function exists in a class.

#include<type_traits>
#include<iostream>

#define TEMPLATE_TYPE FN_TEMPLATE

#define FN_TEMPLATE 0
#define CLASS_TEMPLATE 1
#define CLASS_FN_TEMPLATE 2

/*! The template `has_begin<T,RET_TYPE>` exports a
boolean constant `value that is true iff `T` provides
`RET_TYPE T::begin() const`
*/
template< typename CLASS>
struct has_begin
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A_CLASS>
    static std::true_type test(void(A_CLASS::*)()) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A>
    static decltype(test(&A::begin))
        test(decltype(&A::begin), void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::begin)) return_type;
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type();
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<CLASS>(0, 0)) type;

    static const bool value = type::value; /* Which is it? */
};

struct A
{
    void begin()
    {
        std::cout << "HI" << std::endl;
    }
};

struct B
{
};

#if TEMPLATE_TYPE == FN_TEMPLATE
template<typename T>
typename std::enable_if<has_begin<T>::value>::type
    call()
{
    T().begin();
}

template<typename T>
typename std::enable_if<!has_begin<T>::value>::type
    call()
{
}
#elif TEMPLATE_TYPE == CLASS_TEMPLATE
template<typename T>
    struct test
{
    static
    typename std::enable_if<has_begin<T>::value>::type
        call()
    {
        T().begin();
    }

    static
    typename std::enable_if<!has_begin<T>::value>::type
        call()
    {
    }
};
#elif TEMPLATE_TYPE == CLASS_FN_TEMPLATE
template<typename A>
    struct test
{
template<typename T>
    static
    typename std::enable_if<has_begin<T>::value>::type
        call()
    {
        T().begin();
    }

template<typename T>
    static
    typename std::enable_if<!has_begin<T>::value>::type
        call()
    {
    }
};
#endif

int main()
{
    #if TEMPLATE_TYPE == FN_TEMPLATE
    call<A>();    
    call<B>();    
    #elif TEMPLATE_TYPE == CLASS_TEMPLATE
    test<A>::call();
    test<B>::call();
    #elif TEMPLATE_TYPE == CLASS_FN_TEMPLATE
    test<void>::call<A>();
    test<void>::call<B>();
    #endif
    return 0;
}

demo

If you set TEMPLATE_TYPE to FN_TEMPLATE or CLASS_FN_TEMPLATE all works as expected, the programme compiles outputting "HI" which is the command inside of A::begin(). B::begin() doesn't get called because there is no B::begin() member function.

However, if TEMPLATE_TYPE is set to CLASS_TEMPLATE, this fails to compile citing that both test<X>::call() functions have been instantiated.

Why does SFINAE not work in this case?

Aucun commentaire:

Enregistrer un commentaire