31

After answering this question I was trying to find is_complete template in Boost library and I realized that there is no such template in Boost.TypeTraits. Why there is no such template in Boost library? How it should look like?

//! Check whether type complete
template<typename T>
struct is_complete
{   
  static const bool value = ( sizeof(T) > 0 );
};

...

// so I could use it in such a way
BOOST_STATIC_ASSERT( boost::is_complete<T>::value );

The code above is not correct, because it is illegal to apply sizeof to an incomplete type. What will be a good solution? Is it possible to apply SFINAE in this case somehow?


Well, this problem couldn't be solved in general without violating the ODR rule, but there is there a platform specific solution which works for me.

Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • 15
    I think this cannot work in principle (except when you always apply it to a type that always stays incomplete, or is always complete). Because whether `U` is complete or not, `is_complete` always specifies the same type. If you now go and use `is_complete` in two different translation units, the value member would possibly have a different value each time and the compiler is free in what value it uses. This is not valid i think, but i can't find a statement of the Standard about this :( Would be glad if you could find out. – Johannes Schaub - litb Oct 26 '09 at 15:09
  • The bigger question is why. Without reflection why do you need this as the compiler knows at compiler time. – Martin York Oct 26 '09 at 15:43
  • There is also no way in C++03 this can be done. Atmost with C++0x with "sfinae for expressions" but even then if you pass `vector` for example and `vector` is only declared but not defined, then the check for completeness will yield to an implicit instantiation, and if the definition is not available will issue an hard error that's not covered by sfinae (the error is not in the "immediate context"). – Johannes Schaub - litb Oct 26 '09 at 15:47
  • 1
    @litb: I would say this is handled with a combination of 14.4/1 (Type equivalence for template-id's) and then 3.2/5 bullet 2 which requires that names refer to the same entity. If the same template-id results in a name referring to different entities then that's an ODR violation. – Richard Corden Oct 26 '09 at 16:43
  • @Richard, but the problem seems to me that the entities that class template refers to are always the same (`T` is always the same class) and each case has the same tokens. Refering to `is_complete::value` in another definition that might be covered by 3.2, will also always refer to the same entity (because the static data-member has external linkage, it's always the same object). I would expect it to somewhere say "Each instantiation of a static data-member definition or declaration shall result in the same value used for initialization." – Johannes Schaub - litb Oct 26 '09 at 17:04
  • For the non-template case, there is no problem, because either you would use a different token for initialization, or you would refer to a different entity (like in one case `sizeof(T)` with `T` being `int` and another case with `T` being `char`, or you would refer to an object with internal linkage that you would read a value from and the value differs). But in this template scenario, i can't figure out where such a violation is done. Hmm – Johannes Schaub - litb Oct 26 '09 at 17:08
  • Seems to be answered [here](http://stackoverflow.com/questions/8449036/is-it-possible-to-deduce-whether-type-is-incomplete-without-compilation-failure#8449204), in the answer by KennyTM. –  Aug 01 '12 at 04:30
  • @JohannesSchaub-litb Please review my answer, if you have a sec. – Potatoswatter Jul 16 '13 at 09:18
  • 1
    The compiler knows that a type is here incomplete, and then complete perhaps a few lines later; say after a new declaration or specialisation. Surely that information could, in principle, be made available. – user2023370 Jun 21 '16 at 23:16
  • @JohannesSchaub-litb, what if rather than using a static member for the value an enum were used? Would the ODR rule still have to apply? What would the "Definition" part of "ODR" apply to, in case of an enum? A solution using an enum is provided here: https://stackoverflow.com/a/49676122/566849 – Fabio A. Apr 05 '18 at 15:30
  • You might want to check out [**my answer**](/a/69234765/541686) which works *without a macro*. – user541686 Sep 18 '21 at 12:54

8 Answers8

19

The answer given by Alexey Malistov can be used on MSVC with a minor modification:

namespace 
{
    template<class T, int discriminator>
    struct is_complete {  
      static T & getT();   
      static char (& pass(T))[2]; 
      static char pass(...);   
      static const bool value = sizeof(pass(getT()))==2;
    };
}
#define IS_COMPLETE(X) is_complete<X,__COUNTER__>::value

Unfortunately, the __COUNTER__ predefined macro is not part of the standard, so it would not work on every compiler.

L. F.
  • 19,445
  • 8
  • 48
  • 82
J. Calleja
  • 4,855
  • 2
  • 33
  • 54
  • `__COUNTER__` is supported by MSVC and with gcc starting from 4.3. However, with gcc, the problem isn't really in forging different `is_complete` types with the help of `__COUNTER__` but having the compiler do SFINAE and select the `pass(...)` overload. – Gregory Pakosz Dec 25 '09 at 10:33
  • 1
    This doesn't seem to be compatible with complete abstract types. On MSVC 2008 `IS_COMPLETE(MyAbstractClass)` fails to compile. And even if the compiler did allow this and was able to do the SFINAE with the abstract type, it seems that it would give the incorrect answer (i.e. it would report that a complete abstract type was in fact incomplete). – bshields Jul 05 '11 at 09:22
  • 8
    This violates the ODR rule. The counter gets you multiple different specializations in one translation unit, but if you use it in a second file, the same specialization will get an incompatible definition. The solution is to put it in an unnamed namespace. – Potatoswatter Jul 16 '13 at 08:32
  • 2
    `__LINE__` can be used instead of `__COUNTER__`. `__LINE__` is standard. – anton_rh Jul 26 '21 at 05:26
  • [**My answer**](/a/69234765) works without a macro. – user541686 Sep 18 '21 at 12:45
16

It might be a bit late, but so far, no C++ 11 solution worked for both complete and abstract types.

So, here you are.

With VS2015 (v140), g++ >= 4.8.1, clang >= 3.4, this is working:

template <class T, class = void>
struct IsComplete : std::false_type
{};

template <class T>
struct IsComplete< T, decltype(void(sizeof(T))) > : std::true_type
{};

Thanks to Bat-Ulzii Luvsanbat: https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/

With VS2013 (V120):

namespace Details
{

    template <class T>
    struct IsComplete
    {
        typedef char no;
        struct yes { char dummy[2]; };

        template <class U, class = decltype(sizeof(std::declval< U >())) >
        static yes check(U*);

        template <class U>
        static no check(...);

        static const bool value = sizeof(check< T >(nullptr)) == sizeof(yes);
    };

} // namespace Details


template <class T>
struct IsComplete : std::integral_constant< bool, Details::IsComplete< T >::value >
{};

This one is inspired from the internets and static assert that template typename T is NOT complete?

Community
  • 1
  • 1
mister why
  • 1,967
  • 11
  • 33
  • 3
    Worked perfectly for me on all compilers I tested, and follows a more familiar style for custom type traits, without relying on the implementation specific `__COUNTER__` value. I believe this is a better solution than the accepted answer. – Roger Sanders Aug 14 '18 at 23:20
  • 3
    This doesn't seem to work if I forward declare a type, use this trait, then define it and use it again. It yields false for on both. Tried on gcc.godbolt.org with trunk GCC/Clang. https://gcc.godbolt.org/z/pegeNa – SLC Apr 29 '19 at 12:15
  • 2
    @SanduLiviuCatalin Works fine like this: https://gcc.godbolt.org/z/HFGdZA. So I guess you've found a bug you can workaround by reordering your definitions. I think this is related to the fact the template is evaluated once in this compilation unit. – mister why Sep 11 '19 at 12:03
  • Note that this does not return the correct value for function types because `sizeof` cannot be applied to function types. – Fido Feb 20 '22 at 19:51
9
template<class T>
struct is_complete {
    static T & getT();
    static char (& pass(T))[2];
    static char pass(...);

    static const bool value = sizeof(pass(getT()))==2;
};
Alexey Malistov
  • 26,407
  • 13
  • 68
  • 88
  • 5
    Nice, but as @litb says in his comment, it doesn't work properly if is_complete appears in 2 contradicting locations in the same file, when the definition of class type appears between them (I tried :) ). – Asaf Oct 26 '09 at 15:11
  • fails here with error: initializing argument 1 of ‘static char (& is_complete::pass(T))[2] [with T = Foo]’ – Gregory Pakosz Dec 22 '09 at 23:32
  • It does not work with modern GCC or Clang: https://gcc.godbolt.org/z/z5cYWjeaE – Fedor Jul 29 '21 at 15:06
5

I'm afraid you can't implement such an is_complete type traits. The implementation given by @Alexey fails to compile on G++ 4.4.2 and G++ 4.5.0:

error: initializing argument 1 of ‘static char (& is_complete::pass(T))[2] [with T = Foo]’

On my Mac, with G++ 4.0.1 evaluating is_complete<Foo>::value where struct Foo; is incomplete yields to true which is even worse than a compiler error.

T can be both complete and incomplete in the same program, depending on the translation unit but it's always the same type. As a consequence, as commented above, is_complete<T> is always the same type as well.

So if you respect ODR it is not possible to have is_complete<T> evaluating to different values depending on where it is used; otherwise it would mean you have different definitions for is_complete<T> which ODR forbids.

EDIT: As the accepted answer, I myself hacked around a solution that uses the __COUNTER__ macro to instantiate a different is_complete<T, int> type everytime the IS_COMPLETE macro is used. However, with gcc, I couldn't get SFINAE to work in the first place.

Gregory Pakosz
  • 69,011
  • 20
  • 139
  • 164
  • Another attempt can be found here: https://stackoverflow.com/questions/68573739/how-to-check-that-a-c-class-is-incomplete-only-declared – Fedor Jul 29 '21 at 15:07
4

Solving this requires performing the computation in the default argument of the trait template, as attempting to change the definition of a template violates the ODR rule (although a combination of __COUNTER__ and namespace {} can work around ODR).

This is written in C++11 but can be adjusted to work in C++03 mode of a moderately recent C++11-compatible compiler.

template< typename t >
typename std::enable_if< sizeof (t), std::true_type >::type
is_complete_fn( t * );

std::false_type is_complete_fn( ... );

template< typename t, bool value = decltype( is_complete_fn( (t *) nullptr ) )::value >
struct is_complete : std::integral_constant< bool, value > {};

Online demo.

The default argument is evaluated where the template is named, so it can contextually switch between different definitions. There is no need for a different specialization and definition at each use; you only need one for true and one for false.

The rule is given in §8.3.6/9, which applies equally to function default arguments and default template-arguments:

Default arguments are evaluated each time the function is called.

But beware, using this inside a template is almost sure to violate the ODR. A template instantiated on an incomplete type must not do anything differently from if it were instantiated on a complete type. I personally only want this for a static_assert.

Incidentally, this principle may also be helpful if you want to go the other way and implement the functionality of __COUNTER__ using templates and overloading.

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Question regarding this point: *"A template instantiated on an incomplete type must not do anything differently from if it were instantiated on a complete type."* If we ensure that a template is *not* simultaneously instantiated on *both* the incomplete *and* the complete versions of the same type, is that still an ODR violation, or is that fine? – user541686 Sep 18 '21 at 13:15
  • The requirement is applied at each place the instantiation is used and then again at the end of each translation unit. So no. The principle is that types don’t have changing versions. – Potatoswatter Sep 18 '21 at 14:05
2

Just chiming in to signal that an answer (not given by me) to an unrelated question gives a solution for the is_complete<T> template.

The answer is here. I'm not pasting it below in order to not mistakenly get credit for it.

Fabio A.
  • 2,517
  • 26
  • 35
1

I can't find anything in the standard that guarantees that sizeof on an incomplete type will yield 0. It does require, however, that if T is incomplete at some point, but completed later in that translation unit, that all references to T refer to the same type -- so as I read it, even if T is incomplete where your template was invoked, it would be required to say it was complete if T is completed somewhere in that translation unit.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • "_that if T is incomplete at some point, but completed later in that translation unit, that all references to T refer to the same type_" indeed, a user defined type is not **inherently complete or incomplete**, it is only incomplete until it is completed (`void` is inherently incomplete). So the whole idea is extremely problematic. – curiousguy May 24 '14 at 03:25
  • 2
    `sizeof` is ill-formed for incomplete types and cannot yield zero, since C++98: "The `sizeof` operator shall not be applied to an expression that has … incomplete type, … or to the parenthesized name of such types." GCC's treatment is nonconforming, but it's a good idea to support it anyway if writing such a facility. – Potatoswatter Jul 24 '15 at 01:33
  • "so as I read it, even if T is incomplete where your template was invoked, it would be required to say it was complete if T is completed somewhere in that translation unit." -- Apparently no compiler works this way even in the simplest situations (live on godbolt: [example 1](https://godbolt.org/z/Cc9HCD), [example 2](https://godbolt.org/z/2fxnno)). – Dr. Gut Apr 06 '20 at 00:18
1

It's an old question, but the proposed answers doesn't work correctly for some types such as function reference type or cv-qualified function types.

template<typename T, typename = void>
struct is_complete_object : std::false_type {};

template<typename T>
struct is_complete_object<T, std::enable_if_t<(sizeof(T) > 0)>> : std::true_type {};

template<typename T, bool v = std::is_object<T>::value /* true */>
struct is_complete_impl : is_complete_object<T> {};

template<typename T>
struct is_complete_impl<T, false> : std::integral_constant<bool, !std::is_void<T>::value> {};

template <typename T>
struct is_complete : is_complete_impl<T> {};

template<typename T>
struct is_complete<T[]> : std::false_type {};

template<typename T, size_t N>
struct is_complete<T[N]> : is_complete<T> {};

Now this will work for function-like types.

김선달
  • 1,485
  • 9
  • 23