31

I have the following code:

#include <iostream>

class A;

int main()
{
    std::cout << std::is_constructible<A>::value << std::endl;
}

When I use GCC 8.3, this code compiles. However, when I use Clang 8.0, I get a compilation error that incomplete types cannot be used in type traits.

Which one is correct? Am I allowed to use is_constructible on an incomplete type (with an expected value of false), or am I not allowed to?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
R_Kapp
  • 2,818
  • 1
  • 18
  • 32
  • Related question [SFINAE with std::enable\_if and std::is\_default\_constructible for incomplete type in libc++](https://stackoverflow.com/questions/40821950/sfinae-with-stdenable-if-and-stdis-default-constructible-for-incomplete-type) – t.niese Apr 24 '19 at 13:47
  • How would `is_constructible` determine if `A` is constructible if `A` is not a complete type? – François Andrieux Apr 24 '19 at 13:56
  • 1
    @FrançoisAndrieux: If `A` isn't complete, we can't construct it (`A a;` on an incomplete class `A` causes a compiler error) - that's the way it was working in our code base in the past, but it appears that was just luck of the draw due to undefined behavior. – R_Kapp Apr 24 '19 at 13:59
  • 3
    Re: "we can't construct it" -- type traits are about the properties of a type. It would be really disturbing if `std::is_constuctible` changed its result depending on what headers you included. In one place it's constructible and in another it isn't? – Pete Becker Apr 24 '19 at 14:33
  • @PeteBecker: That's a fair point that I hadn't considered - the original use case was a templated `class A` that only had specific parameters instantiated. We then used SFINAE with `std::is_constructible` so that only certain types of types were allowed through. This is easily done correctly by explicitly instantiating a default `class A` where constructors are explicitly deleted (which is what I've done), but the thought process was from that angle. Hadn't considered the (what should have been obvious) "forward-declared-class-that-actually-exists" angle... – R_Kapp Apr 24 '19 at 19:10

3 Answers3

25

The behavior is undefined.

[meta.unary.prop]

template <class T, class... Args> struct is_constructible;

T and all types in the parameter pack Args shall be complete types, (possibly cv-qualified) void, or arrays of unknown bound.

That's a precondition of the meta-function. A contract that your code violates. libc++ is being generous by notifying you.


Mind you, that putting that precondition there and leaving it undefined otherwise is for a reason. A program where two points of instantiation of a template have different meanings is ill-formed NDR. The only sane course of action is demand complete types. And after all, that's when the trait is most useful anyway.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
17

Your code causes undefined behavior.

Cppreference states:

template< class T, class... Args > struct is_constructible;

T and all types in the parameter pack Args shall each be a complete type, (possibly cv-qualified) void, or an array of unknown bound. Otherwise, the behavior is undefined.

Nellie Danielyan
  • 1,001
  • 7
  • 19
8

Your code has undefined behavior. Per [meta.unary.prop] table 47 std::is_constructible requires

T and all types in the template parameter pack Args shall be complete types, cv void, or arrays of unknown bound.

emphasis mine

NathanOliver
  • 171,901
  • 28
  • 288
  • 402