12

My current question has been inspired by attempting to understand how std::unique_ptr<T, D> utilizes template mechanics to instantiate a template class the size of a T* when D (the deleter type) is a lambda function type, but a larger size when D is a function pointer type (since space needs to be allocated in the unique_ptr instance to store the function pointer).

Looking through the VS2015 source code, I find that std::unique_ptr derives from std::_Unique_ptr_base, which in turn declares a data member of type _Compressed_pair<class _Ty1, class _Ty2, bool = is_empty<_Ty1>::value && !is_final<_Ty1>::value>. The type _Ty1 in this latter context is the type of the deleter, D, that is the second unique_ptr template parameter noted in the previous paragraph; i.e., the motivation behind this question is that I am contrasting _Ty1 being a lambda type, vs. _Ty1 being a function pointer type. (In fact, the default value of the bool is being utilized.)

I recognize that is_empty<_Ty1>::value is true when _Ty1 is an instance of a lambda type (when the lambda has no capture variables and therefore has a 0 size); but that it is false when _Ty1 is a function pointer type.

This led me to pursue how std::is_empty is defined.

Ugh!

What follows is the complete implementation of std::is_empty that I can find in the VS2015 C++ library source code.

In the file type_traits is this:

// TEMPLATE CLASS is_empty template<class _Ty> struct is_empty _IS_EMPTY(_Ty) { // determine whether _Ty is an empty class }; 

... and the macro _IS_EMPTY is defined in the same file:

#define _IS_EMPTY(_Ty) \ : _Cat_base<__is_empty(_Ty)> 

... and at this point my luck runs dry, because I cannot find the definition of __is_empty anywhere. I have GREPped through the entire VS2015 installation directory (which includes, I think, all of the C++ library source code, in - though perhaps I'm mistaken).

I like to understand C++ internals when I want to. But ... I'm stuck on this one, and plenty of googling did not reveal the answer (though I have seen reference to intrinsics), and my digging has not ... discovered any source code.

Can someone enlighten this situation? How is std::is_empty<T> actually implemented in VS2015, or, for that matter, any other compiler?

2
  • I'm not sure of it, but according to the standard, an empty class has the size of 1 byte. one can measure the size of the class and determine. I haven't really tried to implement this Commented Feb 21, 2016 at 8:56
  • @DavidHaim - That reasoning is related to the answer that Dietmar Kühl provides. In the example in his answer, he takes advantage of the fact that when a non-empty class derives from an empty class, the empty class subobject does have a size of 0 - it's only when the class is completely empty including all base class subobjects that the class must have a non-zero size. Commented Feb 21, 2016 at 12:25

2 Answers 2

14

Looks as if MSVC++ provides an intrinsic __isempty(T) rather than dealing with a library-level implementation. Since the argument type T passed to std::is_empty<T> can be final, I don't think there can be a safe library implementation and compiler help may be needed.

The only way to determine if a type T is empty in a library I can think of is this (the specialization deals with non-class types for which std::is_empty<T> is not true):

template <bool, typename T> struct is_empty_aux: T { unsigned long long dummy; }; template <typename T> struct is_empty_aux<false, T> { unsigned long long dummy[2]; }; template <typename T> struct is_empty: std::integral_constant<bool, sizeof(is_empty_aux<std::is_class<T>::value, T>) == sizeof(unsigned long long)> { }; 

However, if T is final the inheritance in is_empty_aux is illegal. While the case of a final class can be detected using std::is_final<T> I don't see a way to determine whether its objects are empty. Thus, using a compiler intrinsic may be necessary. Compiler intrinsics are certainly necessary for some of the other type traits anyway. Compiler intrinsics are declarations/definitions somehow magically provided by the compiler: they are normally neither explicitly declared nor defined. The compiler has the necessary knowledge about types and exposing this knowledge via intrinsics is a reasonable approach.

Sign up to request clarification or add additional context in comments.

13 Comments

Thanks for this! One aspect of the template mechanism that I am trying to understand, though, is how the std::is_empty<T> is able to also successfully handle non-class types such as int or void(*)(). That is to say, std::is_empty<int> and std::is_empty<void(*)()> are both valid. However, your small sample code will not compile when T is an int or function pointer. I know that is incidental to the core point you've made in your answer, but I am trying to understand that aspect of template mechanics, as well. Any way you could incorporate non-class types for T in this answer?
@DanNissenbaum: Note that a lambda without a capture is not a function and/or function pointer: it is still a class type however one which can convert to a function pointer.
This also breaks if the class has a sufficiently large number of empty base class subobjects of the same type. e.g., given struct X {}; template<int> struct Y : X {}; template<int... Ns> struct Z : Y<Ns>... {};, Z<1,2,3,4,5,6,7,8,9,10>.
@DanNissenbaum: std::is_class can be implemented without intrinsic, I think. The built-in types are easy to detect (it is a typing exercise for the numeric types which are specialized individually for some trait and uses a few specialization for the pointer and reference types). I recall that there were some tricks to detect enums and unions. However, there are some type traits which do require intrinsics, e.g., std::has_virtual_destructor.
@DanNissenbaum Not quite. struct A : Z<1, 2> { short i; }; should take up only two bytes. A class with N empty base class subobjects of the same type must take up at least N bytes, but if the actual data members occupy at least N bytes, then the base class subobjects don't need to contribute to the final size.
|
2

Also ran across this question and took a look at the gcc 4.8 headers in Ubuntu 14.04. In fact there are a family of them, like __is_empty, __is_pod, __is_polymorphic, etc, used in this way

// /usr/include/c++/4.8/type_traits:516 template<typename _Tp> struct is_empty : public integral_constant<bool, __is_empty(_Tp)> {}; 

They seem to work without including any C++ headers. I've tried g++ and clang++ to compile this code

#include <stdio.h> struct X {}; int main() { printf("%d %d %d\n", __is_empty(X), __is_enum(X), __is_class(X)); return 0; } 

What I feel uncommon is that they look like functions but actually take a type as argument, instead of an instance (not work if you try X x; __is_empty(x);).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.