Standards interpretation question wrt to Clang trunk

Is this the right alias for discussion of Clang implementation specifics? Or would that be better over on cfe-commits?

Assuming this is an appropriate alias (or that I can simply forward this on if not), I am fixing some missing compiler intrinsics that Clang doesn’t provide but the VS 2012 STL headers require for some of their type traits implementations.

In doing this I have found a couple of things in Clang’s current type trait support that seem incorrect by my reading of the standards, but I wanted to run this by other people before I changed anything.

The first issue is this code:

struct NotTrivial {
NotTrivial() = delete;
};

int main(int,char**)
{
static_assert(!__is_trivial(NotTrivial), “A class with no default constructor doesn’t meet the definition of a trivial class”);
return 0;
}

This fires today in Clang trunk.

Looking at the standard I see this:

The definition of a trivial type is n3376 3.9 [basic.types]/9

Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified
versions of these types (3.9.3) are collectively called scalar types. Scalar types, POD classes (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called POD types. Scalar types, trivially copyable class types (Clause 9), arrays of such types, and cv-qualified versions of these types (3.9.3) are collectively called trivially copyable types. Scalar types, trivial class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called trivial types. Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified
versions of these types (3.9.3) are collectively called standard layout types.

The referenced Clause 9 says (n3376 9 [class]/6):

A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable.
[ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base

Since NotTrivial has no default constructor it seems it can’t meet this definition.

I believe the issue lies in clang\lib\AST\DeclCXX.cpp (line 579):

bool UserProvided = Constructor->isUserProvided();

if (Constructor->isDefaultConstructor()) {

data().DeclaredDefaultConstructor = true;

if (UserProvided) {

// C++0x [class.ctor]p5:
// A default constructor is trivial if it is not user-provided […]
data().HasTrivialDefaultConstructor = false;
data().UserProvidedDefaultConstructor = true;
}

In this case isUserProvided is defined as:

/// isUserProvided - True if this method is user-declared and was not
/// deleted or defaulted on its first declaration.
bool isUserProvided() const {
return !(isDeleted() || getCanonicalDecl()->isDefaulted());
}

so we never fall into the block that would set HasTrivialDefaultConstructor to false, since UserProvided ends up as false. We also set DeclaredDefaultConstructor to true even though Constructor->isDeleted() would return true here.

The second issue (this one I am less sure about) is in this code (this also fires in Clang trunk):

struct NotTriviallyCopyable {
NotTriviallyCopyable(const NotTriviallyCopyable&) = delete;
NotTriviallyCopyable& operator=(const NotTriviallyCopyable&) = delete;
};

int main(int,char**)
{
static_assert(!__is_trivially_copyable(NotTriviallyCopyable), “A class with no copy constructor or copy assignment operator is not trivially copyable”);
return 0;
}

This one hinges on the definition of trivially-copyable, which seems like it can be read two ways:

n3376 9 [class]/6:

A trivially copyable class is a class that:
— has no non-trivial copy constructors (12.8),
— has no non-trivial move constructors (12.8),
— has no non-trivial copy assignment operators (13.5.3, 12.8),
— has no non-trivial move assignment operators (13.5.3, 12.8), and
— has a trivial destructor (12.4).

It seems since this class has NO copy constructors or copy assignment operators that it shouldn’t be considered trivially copyable.

That said, you could also read this as the fact there are no such operators means, implicitly, that there are no non-trivial versions of said operators and thus this class IS trivially copyable. That reading doesn’t really seem to yield interesting type information since you can’t actually copy a type such as this (short of just doing a memcpy). Or is the intention of code dealing with trivial types WOULD just copy say via std::memcpy?

Thoughts? Am I misreading the standard? Misunderstanding the Clang code here?

Ryan

Is this the right alias for discussion of Clang implementation specifics? Or
would that be better over on cfe-commits?

This is the right place.

Assuming this is an appropriate alias (or that I can simply forward this on
if not), I am fixing some missing compiler intrinsics that Clang doesn't
provide but the VS 2012 STL headers require for some of their type traits
implementations.

In doing this I have found a couple of things in Clang's current type trait
support that seem incorrect by my reading of the standards, but I wanted to
run this by other people before I changed anything.

The first issue is this code:

struct NotTrivial {
  NotTrivial() = delete;
};

int main(int,char**)
{
    static_assert(!__is_trivial(NotTrivial), "A class with no default
constructor doesn't meet the definition of a trivial class");
    return 0;
}

This fires today in Clang trunk.

Looking at the standard I see this:

The definition of a trivial type is n3376 3.9 [basic.types]/9

Arithmetic types (3.9.1), enumeration types, pointer types, pointer to
member types (3.9.2), std::nullptr_t, and cv-qualified
versions of these types (3.9.3) are collectively called scalar types. Scalar
types, POD classes (Clause 9), arrays of such types and cv-qualified
versions of these types (3.9.3) are collectively called POD types. Scalar
types, trivially copyable class types (Clause 9), arrays of such types, and
cv-qualified versions of these types (3.9.3) are collectively called
trivially copyable types. Scalar types, trivial class types (Clause 9),
arrays of such types and cv-qualified versions of these types (3.9.3) are
collectively called trivial types. Scalar types, standard-layout class types
(Clause 9), arrays of such types and cv-qualified
versions of these types (3.9.3) are collectively called standard layout
types.

The referenced Clause 9 says (n3376 9 [class]/6):

A trivial class is a class that has a trivial default constructor (12.1) and
is trivially copyable.
[ Note: In particular, a trivially copyable or trivial class does not have
virtual functions or virtual base

Since NotTrivial has no default constructor it seems it can't meet this
definition.

Clang is behaving correctly. NotTrivial has a default constructor,
which is deleted, and thus not user-provided. It meets the other
constraints for triviality, so it is a trivial default constructor.

(Note that deleted functions still exist, and can still be trivial.)

The second issue (this one I am less sure about) is in this code (this also
fires in Clang trunk):

struct NotTriviallyCopyable {
  NotTriviallyCopyable(const NotTriviallyCopyable&) = delete;
  NotTriviallyCopyable& operator=(const NotTriviallyCopyable&) = delete;
};

int main(int,char**)
{
    static_assert(!__is_trivially_copyable(NotTriviallyCopyable), "A class
with no copy constructor or copy assignment operator is not trivially
copyable");
    return 0;
}

This one hinges on the definition of trivially-copyable, which seems like it
can be read two ways:

n3376 9 [class]/6:

A trivially copyable class is a class that:
  — has no non-trivial copy constructors (12.8),
  — has no non-trivial move constructors (12.8),
  — has no non-trivial copy assignment operators (13.5.3, 12.8),
  — has no non-trivial move assignment operators (13.5.3, 12.8), and
  — has a trivial destructor (12.4).

It seems since this class has NO copy constructors or copy assignment
operators that it shouldn't be considered trivially copyable.

Clang is correct here too. This class has a trivial (deleted) copy
constructor and a trivial (deleted) copy assignment. It has no move
constructor and no move assignment. It also has a trivial destructor.
Therefore it is trivially copyable.

Interesting. My conceptualization of deleted functions was that they any reference to them in code would be ill-formed. It isn’t clear how one uses some of the type traits (like is_default_constructible, is_copy_constructible, etc…) in meaningful ways in the face of deleted functions. It seems like it would lead one down a path of choosing functions/code paths that end up being ill-formed because the thing you tested for and were told existed by the compiler was actually deleted.

Another example, I notice that UTT_HasTrivialDefaultConstructor maps to the intrinsic __has_trivial_constructor, which reports true for a class with a deleted default constructor. Wouldn’t that naturally lead one to think code like T t; is valid, when it would not be?

Ryan

Some traits are more useful than others. I recommend std::is_trivially_default_constructible, std::is_trivially_copy_constructible and std::is_trivially_copy_assignable for answering the questions you are asking.

Howard

Thanks to both of you for the clarifications.

Ryan