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
.
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
.
1 Answer
Reset to default 2Using = 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>>);