Skip to content

[libc++] P2641R4: Checking if a union alternative is active (std::is_within_lifetime) #107450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libcxx/docs/FeatureTestMacroTable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_is_virtual_base_of`` ``202406L``
---------------------------------------------------------- -----------------
``__cpp_lib_is_within_lifetime`` *unimplemented*
``__cpp_lib_is_within_lifetime`` ``202306L``
---------------------------------------------------------- -----------------
``__cpp_lib_linalg`` *unimplemented*
---------------------------------------------------------- -----------------
Expand Down
1 change: 1 addition & 0 deletions libcxx/docs/ReleaseNotes/20.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Implemented Papers
- P2747R2: ``constexpr`` placement new (`Github <https://github.com/llvm/llvm-project/issues/105427>`__)
- P2609R3: Relaxing Ranges Just A Smidge (`Github <https://github.com/llvm/llvm-project/issues/105253>`__)
- P2985R0: A type trait for detecting virtual base classes (`Github <https://github.com/llvm/llvm-project/issues/105432>`__)
- P2641R4: Checking if a ``union`` alternative is active (`Github <https://github.com/llvm/llvm-project/issues/105381>`__)


Improvements and New Features
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"`P2874R2 <https://wg21.link/P2874R2>`__","Mandating Annex D Require No More","2023-06 (Varna)","","",""
"`P2757R3 <https://wg21.link/P2757R3>`__","Type-checking format args","2023-06 (Varna)","","",""
"`P2637R3 <https://wg21.link/P2637R3>`__","Member ``visit``","2023-06 (Varna)","|Complete|","19.0",""
"`P2641R4 <https://wg21.link/P2641R4>`__","Checking if a ``union`` alternative is active","2023-06 (Varna)","","",""
"`P2641R4 <https://wg21.link/P2641R4>`__","Checking if a ``union`` alternative is active","2023-06 (Varna)","|Complete|","20.0",""
"`P1759R6 <https://wg21.link/P1759R6>`__","Native handles and file streams","2023-06 (Varna)","|Complete|","18.0",""
"`P2697R1 <https://wg21.link/P2697R1>`__","Interfacing ``bitset`` with ``string_view``","2023-06 (Varna)","|Complete|","18.0",""
"`P1383R2 <https://wg21.link/P1383R2>`__","More ``constexpr`` for ``<cmath>`` and ``<complex>``","2023-06 (Varna)","","",""
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,7 @@ set(files
__type_traits/is_valid_expansion.h
__type_traits/is_void.h
__type_traits/is_volatile.h
__type_traits/is_within_lifetime.h
__type_traits/lazy.h
__type_traits/make_32_64_or_128_bit.h
__type_traits/make_const_lvalue_ref.h
Expand Down
36 changes: 36 additions & 0 deletions libcxx/include/__type_traits/is_within_lifetime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___TYPE_TRAITS_IS_WITHIN_LIFETIME_H
#define _LIBCPP___TYPE_TRAITS_IS_WITHIN_LIFETIME_H

#include <__config>
#include <__type_traits/is_function.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER >= 26 && __has_builtin(__builtin_is_within_lifetime)
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI consteval bool is_within_lifetime(const _Tp* __p) noexcept {
if constexpr (is_function_v<_Tp>) {
// Avoid multiple diagnostics
static_assert(!is_function_v<_Tp>, "std::is_within_lifetime<T> cannot explicitly specify T as a function type");
return false;
} else {
Comment on lines +24 to +28
Copy link
Member

Choose a reason for hiding this comment

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

This is clever, but I think I'd rather use the naive way we consistently do this, and just have static_assert at the start of the function. If we think the duplicate diagnostics are bad, that's something we can (and should) fix in Clang (e.g. when it hits a static_assert it should probably stop).

There have been past discussions about this and I'm not really ready to start working around the issue in the library, I don't think that's the right place to do this.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the builtin already diagnoses this, why do we add the static_assert at all?

Copy link
Member

Choose a reason for hiding this comment

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

It would be possible for the builtin on other implementations not to produce a clear diagnostic, I guess. I'm mostly neutral on this FWIW.

Copy link
Contributor

Choose a reason for hiding this comment

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

If that's a problem we can still add a static assert and file a bug against GCC though. FWIW I've filed a bug for __builtin_launder against GCC some time ago and they agreed it's a bug and fixed it fairly quickly.

return __builtin_is_within_lifetime(__p);
}
}
#endif

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___TYPE_TRAITS_IS_WITHIN_LIFETIME_H
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,7 @@ module std_private_type_traits_is_void [system
export std_private_type_traits_integral_constant
}
module std_private_type_traits_is_volatile [system] { header "__type_traits/is_volatile.h" }
module std_private_type_traits_is_within_lifetime [system] { header "__type_traits/is_within_lifetime.h" }
Copy link
Member

Choose a reason for hiding this comment

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

When you rebase onto main, I would recommend you discard the changes to the modulemap entirely and then re-add them again. It's going to be less confusing than resolving the merge conflict.

module std_private_type_traits_lazy [system] { header "__type_traits/lazy.h" }
module std_private_type_traits_make_32_64_or_128_bit [system] { header "__type_traits/make_32_64_or_128_bit.h" }
module std_private_type_traits_make_const_lvalue_ref [system] { header "__type_traits/make_const_lvalue_ref.h" }
Expand Down
8 changes: 8 additions & 0 deletions libcxx/include/type_traits
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ namespace std
template<class B>
inline constexpr bool negation_v = negation<B>::value; // C++17

// [meta.const.eval], constant evaluation context
constexpr bool is_constant_evaluated() noexcept; // C++20
template<class T>
consteval bool is_within_lifetime(const T*) noexcept; // C++26
}

*/
Expand Down Expand Up @@ -517,6 +521,10 @@ namespace std
# include <__type_traits/unwrap_ref.h>
#endif

#if _LIBCPP_STD_VER >= 26
# include <__type_traits/is_within_lifetime.h>
#endif

#include <version>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
Expand Down
4 changes: 3 additions & 1 deletion libcxx/include/version
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,9 @@ __cpp_lib_void_t 201411L <type_traits>
# if __has_builtin(__builtin_is_virtual_base_of)
# define __cpp_lib_is_virtual_base_of 202406L
# endif
// # define __cpp_lib_is_within_lifetime 202306L
# if __has_builtin(__builtin_is_within_lifetime)
# define __cpp_lib_is_within_lifetime 202306L
# endif
// # define __cpp_lib_linalg 202311L
# undef __cpp_lib_mdspan
# define __cpp_lib_mdspan 202406L
Expand Down
3 changes: 3 additions & 0 deletions libcxx/modules/std/type_traits.inc
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ export namespace std {

// [meta.const.eval], constant evaluation context
using std::is_constant_evaluated;
#if _LIBCPP_STD_VER >= 26 && __has_builtin(__builtin_is_within_lifetime)
using std::is_within_lifetime;
#endif

// [depr.meta.types]
using std::aligned_storage;
Expand Down
26 changes: 26 additions & 0 deletions libcxx/test/libcxx/utilities/meta/is_within_lifetime.verify.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
// UNSUPPORTED: clang-18, clang-19, gcc-14, apple-clang-16

// <type_traits>

// LWG4138 <https://cplusplus.github.io/LWG/issue4138>
// std::is_within_lifetime shouldn't work when a function type is
// explicitly specified, even if it isn't evaluated

#include <type_traits>

template <class T>
consteval bool checked_is_within_lifetime(T* p) {
return p ? std::is_within_lifetime<T>(p) : false;
}
static_assert(!checked_is_within_lifetime<int>(nullptr));
static_assert(!checked_is_within_lifetime<void()>(nullptr));
// expected-error@*:* {{static assertion failed due to requirement '!is_function_v<void ()>': std::is_within_lifetime<T> cannot explicitly specify T as a function type}}
Original file line number Diff line number Diff line change
Expand Up @@ -870,16 +870,16 @@
# endif
# endif

# if !defined(_LIBCPP_VERSION)
# if __has_builtin(__builtin_is_within_lifetime)
# ifndef __cpp_lib_is_within_lifetime
# error "__cpp_lib_is_within_lifetime should be defined in c++26"
# endif
# if __cpp_lib_is_within_lifetime != 202306L
# error "__cpp_lib_is_within_lifetime should have the value 202306L in c++26"
# endif
# else // _LIBCPP_VERSION
# else
# ifdef __cpp_lib_is_within_lifetime
# error "__cpp_lib_is_within_lifetime should not be defined because it is unimplemented in libc++!"
# error "__cpp_lib_is_within_lifetime should not be defined when the requirement '__has_builtin(__builtin_is_within_lifetime)' is not met!"
# endif
# endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7186,16 +7186,16 @@
# endif
# endif

# if !defined(_LIBCPP_VERSION)
# if __has_builtin(__builtin_is_within_lifetime)
# ifndef __cpp_lib_is_within_lifetime
# error "__cpp_lib_is_within_lifetime should be defined in c++26"
# endif
# if __cpp_lib_is_within_lifetime != 202306L
# error "__cpp_lib_is_within_lifetime should have the value 202306L in c++26"
# endif
# else // _LIBCPP_VERSION
# else
# ifdef __cpp_lib_is_within_lifetime
# error "__cpp_lib_is_within_lifetime should not be defined because it is unimplemented in libc++!"
# error "__cpp_lib_is_within_lifetime should not be defined when the requirement '__has_builtin(__builtin_is_within_lifetime)' is not met!"
# endif
# endif

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
// UNSUPPORTED: clang-18, clang-19, gcc-14, apple-clang-16

// <type_traits>

// template <class T>
// consteval bool is_within_lifetime(const T*) noexcept; // C++26

#include <type_traits>
#include <cassert>

#include "test_macros.h"

ASSERT_SAME_TYPE(decltype(std::is_within_lifetime(std::declval<int*>())), bool);
ASSERT_SAME_TYPE(decltype(std::is_within_lifetime(std::declval<const int*>())), bool);
ASSERT_SAME_TYPE(decltype(std::is_within_lifetime(std::declval<void*>())), bool);
ASSERT_SAME_TYPE(decltype(std::is_within_lifetime(std::declval<const void*>())), bool);

ASSERT_NOEXCEPT(std::is_within_lifetime(std::declval<int*>()));
ASSERT_NOEXCEPT(std::is_within_lifetime(std::declval<const int*>()));
ASSERT_NOEXCEPT(std::is_within_lifetime(std::declval<void*>()));
ASSERT_NOEXCEPT(std::is_within_lifetime(std::declval<const void*>()));

template <class T>
concept is_within_lifetime_exists = requires(T t) { std::is_within_lifetime(t); };

struct S {};

static_assert(is_within_lifetime_exists<int*>);
static_assert(is_within_lifetime_exists<const int*>);
static_assert(is_within_lifetime_exists<void*>);
static_assert(is_within_lifetime_exists<const void*>);
static_assert(!is_within_lifetime_exists<int>); // Not a pointer
static_assert(!is_within_lifetime_exists<decltype(nullptr)>); // Not a pointer
static_assert(!is_within_lifetime_exists<void() const>); // Not a pointer
static_assert(!is_within_lifetime_exists<int S::*>); // Doesn't accept pointer-to-data-member
static_assert(!is_within_lifetime_exists<void (S::*)()>); // Doesn't accept pointer-to-member-function
static_assert(!is_within_lifetime_exists<void (*)()>); // Doesn't match `const T*`

consteval bool f() {
// Test that it works with global variables whose lifetime is in a
// different constant expression
{
static constexpr int i = 0;
static_assert(std::is_within_lifetime(&i));
// (Even when cast to a different type)
static_assert(std::is_within_lifetime(const_cast<int*>(&i)));
static_assert(std::is_within_lifetime(static_cast<const void*>(&i)));
static_assert(std::is_within_lifetime(static_cast<void*>(const_cast<int*>(&i))));
static_assert(std::is_within_lifetime<const int>(&i));
static_assert(std::is_within_lifetime<int>(const_cast<int*>(&i)));
static_assert(std::is_within_lifetime<const void>(static_cast<const void*>(&i)));
static_assert(std::is_within_lifetime<void>(static_cast<void*>(const_cast<int*>(&i))));
}

{
static constexpr union {
int member1;
int member2;
} u{.member2 = 1};
static_assert(!std::is_within_lifetime(&u.member1) && std::is_within_lifetime(&u.member2));
}

// Test that it works for varibles inside the same constant expression
{
int i = 0;
assert(std::is_within_lifetime(&i));
// (Even when cast to a different type)
assert(std::is_within_lifetime(const_cast<int*>(&i)));
assert(std::is_within_lifetime(static_cast<const void*>(&i)));
assert(std::is_within_lifetime(static_cast<void*>(const_cast<int*>(&i))));
assert(std::is_within_lifetime<const int>(&i));
assert(std::is_within_lifetime<int>(const_cast<int*>(&i)));
assert(std::is_within_lifetime<const void>(static_cast<const void*>(&i)));
assert(std::is_within_lifetime<void>(static_cast<void*>(const_cast<int*>(&i))));
}
// Anonymous union
{
union {
int member1;
int member2;
};
assert(!std::is_within_lifetime(&member1) && !std::is_within_lifetime(&member2));
member1 = 1;
assert(std::is_within_lifetime(&member1) && !std::is_within_lifetime(&member2));
member2 = 1;
assert(!std::is_within_lifetime(&member1) && std::is_within_lifetime(&member2));
}
// Variant members
{
struct X {
union {
int member1;
int member2;
};
} x;
assert(!std::is_within_lifetime(&x.member1) && !std::is_within_lifetime(&x.member2));
x.member1 = 1;
assert(std::is_within_lifetime(&x.member1) && !std::is_within_lifetime(&x.member2));
x.member2 = 1;
assert(!std::is_within_lifetime(&x.member1) && std::is_within_lifetime(&x.member2));
}
// Unions
{
union X {
int member1;
int member2;
} x;
assert(!std::is_within_lifetime(&x.member1) && !std::is_within_lifetime(&x.member2));
x.member1 = 1;
assert(std::is_within_lifetime(&x.member1) && !std::is_within_lifetime(&x.member2));
x.member2 = 1;
assert(!std::is_within_lifetime(&x.member1) && std::is_within_lifetime(&x.member2));
}
{
S s; // uninitialised
assert(std::is_within_lifetime(&s));
}

return true;
}
static_assert(f());

// Check that it is a consteval (and consteval-propagating) function
// (i.e., taking the address of below will fail because it will be an immediate function)
template <typename T>
constexpr void does_escalate(T p) {
std::is_within_lifetime(p);
}
template <typename T, void (*)(T) = &does_escalate<T>>
constexpr bool check_escalated(int) {
return false;
}
template <typename T>
constexpr bool check_escalated(long) {
return true;
}
static_assert(check_escalated<int*>(0), "");
static_assert(check_escalated<void*>(0), "");
3 changes: 2 additions & 1 deletion libcxx/utils/generate_feature_test_macro_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,8 @@ def add_version_header(tc):
"c++26": 202306 # P2641R4 Checking if a union alternative is active
},
"headers": ["type_traits"],
"unimplemented": True,
"test_suite_guard": "__has_builtin(__builtin_is_within_lifetime)",
"libcxx_guard": "__has_builtin(__builtin_is_within_lifetime)",
},
{
"name": "__cpp_lib_jthread",
Expand Down
Loading