{"id":111254,"date":"2025-06-06T07:00:00","date_gmt":"2025-06-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111254"},"modified":"2025-06-06T09:48:18","modified_gmt":"2025-06-06T16:48:18","slug":"20250606-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250606-00\/?p=111254","title":{"rendered":"Why does C++ think my class is copy-constructible when it can&#8217;t be copy-constructed?"},"content":{"rendered":"<p>Consider the following scenario:<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct Base\r\n{\r\n    \/\/ Default-constructible\r\n    Base() = default;\r\n\r\n    \/\/ Not copy-constructible\r\n    Base(Base const &amp;) = delete;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct Derived : Base&lt;T&gt;\r\n{\r\n    Derived() = default;\r\n    Derived(Derived const&amp; d) : Base<a title=\"Why can't I find the injected name of a templated class's templated base class?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240517-00\/?p=109774\">&lt;T&gt;<\/a>(d) {}\r\n};\r\n\r\n\/\/ This assertion passes?\r\nstatic_assert(\r\n    std::is_copy_constructible_v&lt;Derived&lt;int&gt;&gt;);\r\n<\/pre>\n<p>Why does this assertion pass? It is plainly evident that you cannot copy a <code>Derived&lt;int&gt;<\/code> because doing so will try to copy the <code>Base&lt;int&gt;<\/code>, which is not copyable. Indeed, if you try to copy it, you get an error:<\/p>\n<pre>void example(Derived&lt;int&gt;&amp; d)\r\n{\r\n    Derived&lt;int&gt; d2(d);\r\n    \/\/ msvc: error C2280: 'Base&lt;T&gt;::Base(const Base&lt;T&gt; &amp;)':\r\n    \/\/       attempting to reference a deleted function\r\n    \/\/ gcc:  error: use of deleted function 'Base&lt;T&gt;::Base(const Base&lt;T&gt;&amp;)\r\n    \/\/       [with T = int]'\r\n    \/\/ clang: error: call to deleted constructor of 'Base&lt;int&gt;'\r\n}\r\n<\/pre>\n<p>Okay, so the compiler thinks that <code>Derived&lt;int&gt;<\/code> is copy-constructible, but then when we try to do it, we find out that it isn&#8217;t!<\/p>\n<p>What&#8217;s going on is that the compiler is determining copy-constructibility by checking whether the class has a non-deleted copy constructor. And in the case of <code>Derived&lt;T&gt;<\/code> it does haev a non-deleted copy constructor. You declared it yourself!<\/p>\n<pre>    Derived(Derived const&amp; d) : Base&lt;T&gt;(d) {}\r\n<\/pre>\n<p>So yes, there is a copy constructor. It can&#8217;t be instantiated, but the compiler doesn&#8217;t care. It is going based on what you tell it, and you told it that you can copy it.<\/p>\n<p>After all, another possibly copy constructor would have been<\/p>\n<pre>    Derived(Derived const&amp; d) : Base&lt;T&gt;() {}\r\n<\/pre>\n<p>and this one instantiates successfully. Copying a <code>Derived<\/code> default-constructs the <code>Base<\/code> base class rather than copy-constructing it.<\/p>\n<p>Imagine that we moved the definition out of line.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct Derived : Base&lt;T&gt;\r\n{\r\n    Derived() = default;\r\n    Derived(Derived const&amp; d);\r\n};\r\n<\/pre>\n<p>What should the answer to the question &#8220;Is this copy-constructible?&#8221; be? You don&#8217;t know what the definition is, only its declaration. Should the compiler halt compilation with the error message &#8220;Unable to predict the future&#8221;? But what if you didn&#8217;t want the expose the implementation of the copy constructor in the header file?<\/p>\n<p>The rule for determining copy constructibility is whether a non-deleted copy constructor is present. In the case of <code>Derived<\/code>, it is present. It may not be instantiatable, but that&#8217;s not what <code>is_<wbr \/>copy_<wbr \/>constructible<\/code> looks for.\u00b9<\/p>\n<p>Now, non-copyability inherits by default, so we could have just allowed the copy constructor to be defaulted:<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct Derived : Base&lt;T&gt;\r\n{\r\n    Derived() = default;\r\n    Derived(Derived const&amp; d) <span style=\"border: solid 1px currentcolor;\">= default<\/span>;\r\n};\r\n<\/pre>\n<p>The implicitly-defined or explicitly-defaulted copy constructor is defined as deleted if any base class is not copy-constructible, in which case the declaration is treated as if it had said <code>= delete<\/code>. That <code>= delete<\/code> can be detected by <code>is_<wbr \/>copy_<wbr \/>constructible<\/code> and result in the assertion failing.<\/p>\n<p>But if you come out and make a custom copy constructor that is not deleted, the compiler assumes you will make good on your promise.<\/p>\n<p><b>Related reading<\/b>: <a title=\"Why does std::is_copy_constructible report that a vector of move-only objects is copy constructible?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190926-00\/?p=102924\"> Why does <code>std::<wbr \/>is_<wbr \/>copy_<wbr \/>constructible<\/code> report that a vector of move-only objects is copy constructible<\/a>?<\/p>\n<p>\u00b9 Requiring that the type be complete <i>and all members defined<\/i> is not a reasonable requirement because that would require definitions of all class methods to be present in header files. Your entire program has been reduced to a header-only project.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You said that you had a copy constructor, even though it can&#8217;t be compiled.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111254","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You said that you had a copy constructor, even though it can&#8217;t be compiled.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111254","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111254"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111254\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111254"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111254"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111254"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}