lobelk
lobelk

Reputation: 502

How to check if a class has a protected member function?

Consider this simple piece of C++ code:

class Foo
{
  public:
    Foo() = default;
};

int main() {
    static_assert(std::is_default_constructible_v<Foo>);

    return 0;
}

Foo has public default constructor, so static_assert passes. On the other hand, if public is changed to private, Foo can't be default-constructed, so static_assert does not pass. So far, so good.

Now, if public is changed to protected, Foo is still not default-constructible from main, so static_assert again does not pass. However, if some class Bar was to inherit from Foo, Foo would be default-constructible from within Bar but static_assert would still not pass. This is sensible, but problematic.

Now, my question is: is it possible (and how) to construct class Bar that inherits from Foo, has some important default-construction logic, and is default-constructible only if it can default-construct Foo.

In other words, can Bar be made so that the following code passes:

#include <type_traits>

class Foo
{
  protected:
    Foo() = default;
};

class Doo
{
  protected:
    Doo() = delete;
};

template <typename T>
struct Bar : public T
{
    // some implementation ...
    Bar()
    // some magical `requires` statement here ...
    : T() 
    {
        // some very important logic here!
    };
};

int main() {
    static_assert(std::is_default_constructible_v<Bar<Foo>>);
    static_assert(!std::is_default_constructible_v<Bar<Doo>>);

    return 0;
}

NOTE1: If protected is replaced with public in the preceding code, the solution would be simple: constructor of Bar would just have to require T to be default-constructible. However, it is very important that Foo and Doo have protected constructors since they are never to be used on their own.

NOTE2: This problem has nothing to do with constructors, it can be about any member function. I picked default-constructor solely because there is an existing type trait std::is_default_constructible_v.

Upvotes: 2

Views: 111

Answers (1)

Jarod42
Jarod42

Reputation: 218118

Using = default; would allow to "propagate" the possibility to construct the class:

template <typename T>
struct Bar : public T
{
    Bar() = default;
};

static_assert(std::is_default_constructible_v<Bar<Foo>>);
static_assert(!std::is_default_constructible_v<Bar<Doo>>);

If you want to apply it to another derived, you might use the traits on Bar<T> instead.

And for regular method:

template <typename T>
struct Bar : public T
{
    template <typename U = T>
    auto foo() -> decltype(U::foo()) {}
};

template <typename T>
concept has_foo = requires(T t) { t.foo(); };
static_assert(has_foo<Bar<Foo>>);
static_assert(!has_foo<Bar<Doo>>);

Upvotes: 2

Related Questions