Hydrogen
Hydrogen

Reputation: 321

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

Upvotes: 0

Views: 375

Answers (2)

Jarod42
Jarod42

Reputation: 217810

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.

Upvotes: 4

sklott
sklott

Reputation: 2859

Just for information, you can use simpler detector with C++17:

#define CREATE_MEMBER_DETECTOR(X) \
    template <typename T, typename = void> \
    struct Detect_ ## X : std::false_type {}; \
\
    template <typename T>\
    struct Detect_ ## X<T, std::void_t<decltype(T::X)> > \
        : std::true_type {};

Upvotes: 3

Related Questions