11

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?

Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
  • 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 – David Haim Feb 21 '16 at 08: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. – Dan Nissenbaum Feb 21 '16 at 12:25

2 Answers2

13

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Thanks for this! One aspect of the template mechanism that I am trying to understand, though, is how the `std::is_empty` is able to also successfully handle non-class types such as `int` or `void(*)()`. That is to say, `std::is_empty` and `std::is_empty` 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? – Dan Nissenbaum Feb 21 '16 at 04:47
  • @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. – Dietmar Kühl Feb 21 '16 at 08:46
  • 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 struct Y : X {}; template struct Z : Y... {};`, `Z<1,2,3,4,5,6,7,8,9,10>`. – T.C. Feb 21 '16 at 09:58
  • @DietmarKühl Good point - and the fact that a lambda without a capture is not a function or function pointer explains why `sizeof(std::unique_ptr) == sizeof(T*)`. Note that `std::is_empty::value` is `true`, whereas `std::is_empty::value` is `false` - but the point I'm trying to make is that the latter is a well-defined expression even though `void(*)()` **is** a function pointer type but **not** a class type, indicating that function pointer types are valid template arguments in `std::is_empty`. ...I see you edited your answer! looking now. – Dan Nissenbaum Feb 21 '16 at 11:51
  • @DietmarKühl - Looking over your modified answer, I see that the same question is now shifted to the `std::is_class` type trait. Looking through VS2015, I find that this is also implemented as a compiler intrinsic. Do you think that's the *'end of the road'* or the *'leaf of the tree'* and that `std::is_class<>` must be a compiler intrinsic? Or is there some way to implement `std::is_class<>` in C++ that covers most cases? I think that in the end the answer to the general question reflected in my particular question is that **compiler intrinsics** are fundamentally necessary in C++. – Dan Nissenbaum Feb 21 '16 at 12:18
  • @T.C. - That's fascinating. Can you say a word on why it breaks for a class with, say, 10 empty base class subobjects of the same type (as in your example)? – Dan Nissenbaum Feb 21 '16 at 12:22
  • 1
    @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 `enum`s and `union`s. However, there are some type traits which do require intrinsics, e.g., `std::has_virtual_destructor`. – Dietmar Kühl Feb 21 '16 at 12:48
  • @DanNissenbaum Different objects of the same type need to have different addresses, so a class with 10 such base class subobjects must occupy at least 10 bytes, and that `sizeof` won't work (assuming 8-byte `unsigned long long`). – T.C. Feb 21 '16 at 13:31
  • @T.C. Just to confirm - If there is only **one** base class subobject (for an empty base class) then the base class subobject's contribution to the size of the class is 0; but for **two or more** base class subobjects of that same type, now at least all but one of them must contribute a non-0 amount to the size? – Dan Nissenbaum Feb 21 '16 at 13:36
  • 1
    @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. – T.C. Feb 21 '16 at 13:47
  • @T.C.: actually, it should fail even with just a second empty base of the same type if bases of the same type need to have different addresses. That said, it may be an error in the standard to consider such a base empty as it clearly does require memory. The intention of `std::is_empty` is clearly to determine whether inheriting from this base necessarily increases the footprint (and in practice i think all relevant ABIs do take advantage of the empty base optimization). – Dietmar Kühl Feb 21 '16 at 13:47
  • @DietmarKühl No, I tried 2 before using 10 :) With 2, both base class subobjects can overlap with the `unsigned long long` (they just can't overlap with each other) and not contribute to the final size. – T.C. Feb 21 '16 at 13:49
  • @T.C.: interesting. I thought the empty base optimization specifically allowed a base subobject to have the same address of the first member. I'd need to trawl through the standard to find the constraints, though. – Dietmar Kühl Feb 21 '16 at 13:57
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);).

neuront
  • 9,312
  • 5
  • 42
  • 71