Search code examples
c++templatesc++17sfinae

How to enable/disable a member function according to the existence of a member of its derived class?


I had searched over 10 answers and nothing fits my current situation.

(member detector marcos comes from: http://en.wikibooks.org/wiki/More_C++_Idioms/Member_Detector)

CREATE_MEMBER_DETECTOR(normal);
CREATE_MEMBER_DETECTOR(abnormal);

template <typename T>
struct Weapon
{
    //template <std::enable_if_t<Detect_normal<T>::value, T>* = nullptr>
    template <std::enable_if_t<Detect_abnormal<T>::value, bool> = true>
    void DefaultShoot()
    {
        std::cout << "special\n";
    }

    template <std::enable_if_t<Detect_normal<T>::value, bool> = true>
    void DefaultShoot()
    {
        std::cout << "general\n";
    }
};

struct FOO : public Weapon<FOO>
{
    static constexpr auto normal = 10;

    virtual void Shoot() { DefaultShoot(); }
};

Expect: The template generates a set of functions according to what constexpr I have in my subclasses.
Reality: Generate a bunch of errors and never successfully compiled...

Did anyone have a clue whether doing this in C++17 is possible?

Edit: Does the "else" possible in my situation? This is actually my original intention.

template <typename T>
struct Weapon
{
    template <std::enable_if_t<Detect_abnormal<T>::value, bool> = true>
    void DefaultShoot()
    {
        std::cout << "special\n";
    }

    //template <std::enable_if_t<Detect_normal<T>::value, bool> = true>
    #some magical "else" code if the one above is invalidated.
    void DefaultShoot()
    {
        std::cout << "general\n";
    }
};

Solution

  • You would need the template parameter from the method for SFINAE:

    template <typename T>
    struct Weapon
    {
        template <typename U = T, std::enable_if_t<Detect_abnormal<U>::value, bool> = true>
        void DefaultShoot()
        {
            std::cout << "special\n";
        }
    
        template <typename U = T, std::enable_if_t<Detect_normal<U>::value, bool> = true>
        void DefaultShoot()
        {
            std::cout << "general\n";
        }
    };
    

    Notice that you will have ambiguous call if both are true at the same time.

    As you are in C++17, you might use if constexpr:

    template <typename T>
    struct Weapon
    {
        void DefaultShoot()
        {
            if constexpr (Detect_abnormal<T>::value) {
                std::cout << "special\n";
            } else if constexpr (Detect_normal<T>::value) {
                std::cout << "general\n";
            } // else {}
        }
    };
    

    C++20 would allow to use class template parameter in its requires:

    template <typename T>
    struct Weapon
    {
        void DefaultShoot() requires(Detect_abnormal<T>::value)
        {
            std::cout << "special\n";
        }
    
        void DefaultShoot() requires(Detect_normal<T>::value)
        {
            std::cout << "general\n";
        }
    };
    

    Notice that you will have ambiguous call if both are true at the same time.