views:

526

answers:

4

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.

+1  A: 

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
+3  A: 
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
Nice, but as @litb says in his comment, it doesn't work properly if is_complete<type> appears in 2 contradicting locations in the same file, when the definition of class type appears between them (I tried :) ).
Asaf
Gregory Pakosz
compilling with gcc 4.4.2
Gregory Pakosz
sames with gcc 4.5.0
Gregory Pakosz
+1  A: 

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
+3  A: 

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

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.

J. Calleja
`__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