Given the following C++ code snippet:
#include <type_traits>
#include <iostream>
template <typename T>
struct Bar
{
Bar() = default;
Bar(const Bar&) = delete;
};
template <typename T>
struct Foo : public Bar<T>
{
Foo() = default;
Foo(const Foo& other) : Bar<T>(other) {}
};
int main() {
std::cout << std::is_copy_constructible_v<Bar<int>> << '\n'; // prints 0
std::cout << std::is_copy_constructible_v<Foo<int>> << '\n'; // prints 1
return 0;
}
, does anybody know why Foo
is reported to be copy-constructible when it clearly isn't?
In other words, why is deleted copy constructor of Bar
not propagated through Foo
?
Regarding std::is_constructible
, the standard does say: "Only the validity of the immediate context of the variable definition is considered", but I do not see why would they decide to define it like that.
Finally, does this mean that, without an explicit check within Foo
, it is absolutely impossible to discover that Bar
is not copy-constructible by solely inspecting Foo
, with or without modifying Bar
?
UPDATE:
As it seems my initial question was not clear enough, let me update the post.
I am not asking what is the rule that governs the reported behavior, but rather why are the rules defined as such, since it seems to be unintuitive.
Furthermore, nobody seems to answer my central question: if some function foo
does not know how is Foo
implemented and does not even know that Bar
exists, is it impossible for it to discover that Foo
cannot be copied, or is it inevitably doomed to fail to compile?
I am not asking what is the rule that governs the reported behavior, but rather why are the rules defined as such, since it seems to be unintuitive.
The answer to this is that C++ is built around the idea of separate compilation. Separate translation units can be built independently. We thus also have a distinction between declarations and definitions, and you must have the declaration to use an entity, but often you do not need the definition.
So how do you decide if an operation is valid? Do you need the definition of the entity you are using and then anything it uses and so on... all the way down? Or do you focus on the declaration, what the author has told us is valid?
C++ very consistently chooses to look at the declaration for SFINAE, type traits, and even concepts. Yes, that means the author could have messed up and said a type is copyable when it isn't, but it also means we don't need the body of that function (and then everything it needs to be complete) to decide if it is copyable and that function can live in a source file separately compiled.