Skip to content

Conversation

@cor3ntin
Copy link
Contributor

@cor3ntin cor3ntin commented Oct 9, 2025

Classes with a user provided constructor are still implicit lifetime if they have an implicit, trivial copy ctr.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 9, 2025

@llvm/pr-subscribers-clang

Author: Corentin Jabot (cor3ntin)

Changes

Classes with a user provided constructor are still implicit lifetime if they have an implicit, trivial copy ctr.


Full diff: https://github.com/llvm/llvm-project/pull/162612.diff

2 Files Affected:

  • (modified) clang/lib/Sema/SemaTypeTraits.cpp (+17-7)
  • (modified) clang/test/SemaCXX/type-traits.cpp (+11-1)
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 3e34675cbf064..13f25c453d8ba 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1165,14 +1165,24 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
     const CXXDestructorDecl *Dtor = RD->getDestructor();
     if (UnqualT->isAggregateType() && (!Dtor || !Dtor->isUserProvided()))
       return true;
-    if (RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted())) {
-      for (CXXConstructorDecl *Ctr : RD->ctors()) {
-        if (Ctr->isIneligibleOrNotSelected() || Ctr->isDeleted())
-          continue;
-        if (Ctr->isTrivial())
-          return true;
-      }
+    if (!(RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted())))
+      return false;
+    bool FoundCopyCtr = false;
+    bool FoundMoveCtr = false;
+    for (CXXConstructorDecl *Ctr : RD->ctors()) {
+      FoundCopyCtr = Ctr->isCopyConstructor();
+      FoundMoveCtr = Ctr->isMoveConstructor();
+      if (Ctr->isIneligibleOrNotSelected() || Ctr->isDeleted())
+        continue;
+      if (Ctr->isTrivial())
+        return true;
     }
+    if (!FoundCopyCtr && RD->hasTrivialCopyConstructor() &&
+        !RD->defaultedCopyConstructorIsDeleted())
+      return true;
+    if (!FoundMoveCtr && RD->hasTrivialMoveConstructor() &&
+        !RD->defaultedMoveConstructorIsDeleted())
+      return true;
     return false;
   }
   case UTT_IsIntangibleType:
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 901d510bba847..343529fe81b57 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -2066,7 +2066,17 @@ class UserProvidedConstructor {
     UserProvidedConstructor(const UserProvidedConstructor&)            = delete;
     UserProvidedConstructor& operator=(const UserProvidedConstructor&) = delete;
 };
+struct Ctr {
+    Ctr();
+};
+struct Ctr2 {
+    Ctr2();
+private:
+  NoEligibleTrivialContructor inner;
+};
 
+static_assert(__builtin_is_implicit_lifetime(Ctr));
+static_assert(!__builtin_is_implicit_lifetime(NoEligibleTrivialContructor));
 static_assert(__builtin_is_implicit_lifetime(NonAggregate));
 static_assert(!__builtin_is_implicit_lifetime(DataMemberInitializer));
 static_assert(!__builtin_is_implicit_lifetime(UserProvidedConstructor));
@@ -2076,7 +2086,7 @@ template <typename T>
 class Tpl {
     Tpl() requires false = default ;
 };
-static_assert(!__builtin_is_implicit_lifetime(Tpl<int>));
+static_assert(__builtin_is_implicit_lifetime(Tpl<int>));
 
 #endif
 }

@bolshakov-a
Copy link
Contributor

What about implicit default constructors, like in this case?

struct NonCopyable{
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
};

class C {
    NonCopyable nc;
};

//C c;

static_assert(__builtin_is_implicit_lifetime(C));

FoundCopyCtr = Ctr->isCopyConstructor();
FoundMoveCtr = Ctr->isMoveConstructor();
if (Ctr->isIneligibleOrNotSelected() || Ctr->isDeleted())
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test fails now because short-circuiting here occurs before FoundCopyCtr can be set.

FoundDefaultCtr = Ctr->isDefaultConstructor();
}
if (!FoundDefaultCtr && RD->hasTrivialDefaultConstructor())
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never reached because RD->hasTrivialDefaultConstructor() == true case has been short-circuited above.


class C {
NonCopyable nc;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add a note that the classes should not be used here in any way causing declarations of the implicit methods to be instantiated (or whatever the correct word is).


static_assert(__builtin_is_implicit_lifetime(Ctr));
static_assert(__builtin_is_implicit_lifetime(C));
static_assert(!__builtin_is_implicit_lifetime(NoEligibleTrivialContructor));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ctr2 is not used in this test. Maybe, you wanted to mention it here instead of NoEligibleTrivialContructor?

if (!(RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted())))
return false;
if (RD->hasTrivialDefaultConstructor())
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTT_HasTrivialDefaultConstructor handler checks RD->hasTrivialDefaultConstructor() && !RD->hasNonTrivialDefaultConstructor() for some reason. Not sure if this is really needed.

@bolshakov-a
Copy link
Contributor

Now LGTM, thanks! Btw, do you know what !RD->hasNonTrivialDefaultConstructor() check is needed for? If you do, it would be good to add a corresponding test case. If not, a TODO to investigate it is worth placing in the code.

@cor3ntin
Copy link
Contributor Author

hasNonTrivialDefaultConstructor

you can have

template <typename>
class C {
  C();
  C() requires true;
};

But in the case of __builtin_is_implicit_lifetime we probably want to allow that, actually...

@bolshakov-a
Copy link
Contributor

Looks like your new test cases with MultipleDefaults pass even on the previous code revision (with !RD->hasNonTrivialDefaultConstructor() call).

@cor3ntin
Copy link
Contributor Author

@erichkeane @AaronBallman

if (Ctr->isTrivial())
return true;
}
if (!(RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted())))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love if you could split this out into a 'give it a name' type situation (ie a variable or something to store this bool?). This double indirection plus and is a bit of a rough read :)

@cor3ntin cor3ntin force-pushed the corentin/fix_161163 branch from 8dd01f8 to 346aeca Compare October 15, 2025 08:14
@cor3ntin cor3ntin merged commit bcf9e91 into llvm:main Oct 15, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants