How to write `is_complete` template?

Viewed 4862

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.

9 Answers

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.

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.

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.

With C++17 it is possible to use variadic std::void_t with sizeof operator.

Moreover, with sizeof operator it is also possible to check if a not fully specialized class template "is" (that is would be upon instantiation) complete or not.

You just have to define a dummy tag, which is a complete type.

struct any_t
{
};

template<template<typename> class, typename = void>
struct is_complete : std::false_type
{
};

template<template<typename> class GenericClass>
struct is_complete<GenericClass, std::void_t<decltype(sizeof(GenericClass<any_t>))>> :
  std::true_type
{
};

template<typename T>
struct complete
{
    T t;
    void speak() { return t.speak(); }
};

template<typename>
struct incomplete;

static_assert(is_complete<complete>());
static_assert(!is_complete<incomplete>());

Any invocations of t in complete will cause compilation errors only if you try to instantiate an object.

using t = complete<any_t>; // ok
auto c = complete<any_t>(); // error. any_t has no member `speak()`
Related