0

I am struggling to get the following code to work. This prints "No setAttr" which feels unexpected.

Basically I have a method in Parent class which creates and instance of a child class and checks whether given type has a particular method or not. This works when I make the method (setAttr()) public but doesn't for friend functions.

I understand that this may seem a bit strange, working with a bit of legacy code, the use case I am trying to solve it to hide a particular method (setAttr()) once created, this should only be accessible from the base class which will be responsible for generating instances of child classes

template <typename, typename = void_t<>>
struct has_set_attr_method {
  static constexpr bool value = false;
};
template <typename T>
struct has_set_attr_method<T, void_t<decltype(std::declval<T>().setAttr())>> {
  static constexpr bool value = true;
};


struct Parent {
public:
    template<typename T>
    static void create()    {
        auto obj = T::create();
        if constexpr(has_set_attr_method<T>::value) {
            cout << "has setAttr" << endl;
            obj.toString();
        } else {
            cout << "no setAttr" << endl;
        }
    }
};

struct Child : public Parent {
public:
friend class Parent;
    static auto create() {
        return Child();
    }

private:
    void setAttr(int x) {
    }
};

int main(int argc, char const *argv[]) {
    Parent::create<Child>();
    return 0;
}
jack_carver
  • 1,510
  • 2
  • 13
  • 28
  • 1
    The problem is, `setAttr` is called in `has_set_attr_method`, which is not `friend` of `Child`. – songyuanyao Jul 15 '20 at 08:06
  • @songyuanyao: Not only! He is checking for a function of type void() and not of void(int)! – Klaus Jul 15 '20 at 08:37
  • Off Topic Suggestion: when possible, inherit from `std::true_type` and `std::false_type`. So `template struct has_set_attr_method : public std::false_type {};` and `template struct has_set_attr_method().setAttr(1))>> : public std::true_type {};` – max66 Jul 15 '20 at 08:48
  • My bad, the actual code had the current type void(int), made a mistake when posting it here. – jack_carver Jul 15 '20 at 18:31

1 Answers1

1

There are some mistakes in your code:

First, you are checking for a method with no parms, but you want to check for the void setAttr(int x). If you want to do check for method with int, your test must be something like: decltype(std::declval<T>().setAttr(1) ( See additional int parameter here! )

If you do so, we run in a gcc bug! It complains about a call to a private function inside template declaration. As SFINAE exactly mean that we don't want to rise an error if we have something wrong in declaration of a template, I believe that is a gcc bug. clang compiles without any problem.

BTW: This bug was already discussed here: Private member access in template substitution and SFINAE and here: https://code-examples.net/en/q/25ea9ff

And now also as a bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96204

OK, but the result is still permanently return false.

Why: We simply checking not in context of the current type, so friend has no effect!

Solution: Do the check inside the right context! Simply put the checking templates in the context of the current class.

So full code example ( only working with clang as gcc complains about private in template declaration which feels wrong! )

struct Parent {   
    template <typename, typename = void_t<>>
        struct has_set_attr_method {
            static constexpr bool value = false;
        };
    template <typename T>
        struct has_set_attr_method<T, void_t<decltype(std::declval<T>().setAttr(1))>> {
            static constexpr bool value = true;
        };  

    public:
    template<typename T>
        static void create()    {   
            auto obj = T::create();
            if constexpr(has_set_attr_method<T>::value) {
                cout << "has setAttr" << endl;
            } else {
                cout << "no setAttr" << endl;
            }   
        }   
};  

struct Child : public Parent {
    public:
        //friend class Parent;
        static auto create() {
            return Child();
        }   

    private:
        void setAttr(int x) {
        }
};  

int main(int argc, char const *argv[]) {
    Parent::create<Child>();
    return 0;
}   
Klaus
  • 24,205
  • 7
  • 58
  • 113