[libc++] Implement D2351R0 "Mark all library static cast wrappers as [[nodiscard]]"

These [[nodiscard]] annotations are added as a conforming extension;
it's unclear whether the paper will actually be adopted and make them
mandatory, but they do seem like good ideas regardless.

https://isocpp.org/files/papers/D2351R0.pdf

This patch implements the paper's effect on:
- std::to_integer, std::to_underlying
- std::forward, std::move, std::move_if_noexcept
- std::as_const
- std::identity

The paper also affects (but libc++ does not yet have an implementation of):
- std::bit_cast

Differential Revision: https://reviews.llvm.org/D99895
This commit is contained in:
Arthur O'Dwyer 2021-04-05 14:56:03 -04:00
parent d7eb797ea5
commit 4b7bad9eae
8 changed files with 134 additions and 18 deletions

View File

@ -287,15 +287,15 @@ applications of ``[[nodiscard]]`` takes two forms:
1. Backporting ``[[nodiscard]]`` to entities declared as such by the
standard in newer dialects, but not in the present one.
2. Extended applications of ``[[nodiscard]]``, at the libraries discretion,
2. Extended applications of ``[[nodiscard]]``, at the library's discretion,
applied to entities never declared as such by the standard.
Users may also opt-out of additional applications ``[[nodiscard]]`` using
additional macros.
Applications of the first form, which backport ``[[nodiscard]]`` from a newer
dialect may be disabled using macros specific to the dialect it was added. For
example ``_LIBCPP_DISABLE_NODISCARD_AFTER_CXX17``.
dialect, may be disabled using macros specific to the dialect in which it was
added. For example, ``_LIBCPP_DISABLE_NODISCARD_AFTER_CXX17``.
Applications of the second form, which are pure extensions, may be disabled
by defining ``_LIBCPP_DISABLE_NODISCARD_EXT``.
@ -346,3 +346,10 @@ which no dialect declares as such (See the second form described above).
* ``unique``
* ``upper_bound``
* ``lock_guard``'s constructors
* ``as_const``
* ``forward``
* ``move``
* ``move_if_noexcept``
* ``identity::operator()``
* ``to_integer``
* ``to_underlying``

View File

@ -152,7 +152,7 @@ template <class _Integer>
{ return static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(__lhs) >> __shift)); }
template <class _Integer, class = _EnableByteOverload<_Integer> >
constexpr _Integer
_LIBCPP_NODISCARD_EXT constexpr _Integer
to_integer(byte __b) noexcept { return static_cast<_Integer>(__b); }
}

View File

@ -3213,7 +3213,7 @@ using unwrap_ref_decay_t = typename unwrap_ref_decay<_Tp>::type;
// [func.identity]
struct identity {
template<class _Tp>
constexpr _Tp&& operator()(_Tp&& __t) const noexcept
_LIBCPP_NODISCARD_EXT constexpr _Tp&& operator()(_Tp&& __t) const noexcept
{
return _VSTD::forward<_Tp>(__t);
}

View File

@ -2784,7 +2784,7 @@ _LIBCPP_INLINE_VAR _LIBCPP_CONSTEXPR bool is_destructible_v
// move
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
typename remove_reference<_Tp>::type&&
move(_Tp&& __t) _NOEXCEPT
{
@ -2793,7 +2793,7 @@ move(_Tp&& __t) _NOEXCEPT
}
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_Tp&&
forward(typename remove_reference<_Tp>::type& __t) _NOEXCEPT
{
@ -2801,12 +2801,12 @@ forward(typename remove_reference<_Tp>::type& __t) _NOEXCEPT
}
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
_Tp&&
forward(typename remove_reference<_Tp>::type&& __t) _NOEXCEPT
{
static_assert(!is_lvalue_reference<_Tp>::value,
"can not forward an rvalue as an lvalue");
"cannot forward an rvalue as an lvalue");
return static_cast<_Tp&&>(__t);
}

View File

@ -260,7 +260,7 @@ operator>=(const _Tp& __x, const _Tp& __y)
// move_if_noexcept
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
#ifndef _LIBCPP_CXX03_LANG
typename conditional
<
@ -277,8 +277,11 @@ move_if_noexcept(_Tp& __x) _NOEXCEPT
}
#if _LIBCPP_STD_VER > 14
template <class _Tp> constexpr add_const_t<_Tp>& as_const(_Tp& __t) noexcept { return __t; }
template <class _Tp> void as_const(const _Tp&&) = delete;
template <class _Tp>
_LIBCPP_NODISCARD_EXT constexpr add_const_t<_Tp>& as_const(_Tp& __t) noexcept { return __t; }
template <class _Tp>
void as_const(const _Tp&&) = delete;
#endif
struct _LIBCPP_TEMPLATE_VIS piecewise_construct_t { explicit piecewise_construct_t() = default; };
@ -1636,7 +1639,7 @@ __to_underlying(_Tp __val) noexcept {
#if _LIBCPP_STD_VER > 20
template <class _Tp>
_LIBCPP_INLINE_VISIBILITY constexpr underlying_type_t<_Tp>
_LIBCPP_NODISCARD_EXT _LIBCPP_INLINE_VISIBILITY constexpr underlying_type_t<_Tp>
to_underlying(_Tp __val) noexcept {
return _VSTD::__to_underlying(__val);
}

View File

@ -23,9 +23,11 @@
// be listed in `UsingLibcxx.rst` in the documentation for the extension.
#include <algorithm>
#include <functional>
#include <cstddef> // to_integer
#include <functional> // identity
#include <iterator>
#include <memory>
#include <utility> // to_underlying
#include "test_macros.h"
@ -33,7 +35,7 @@ struct P {
bool operator()(int) const { return false; }
};
int main(int, char**) {
void test_algorithms() {
int arr[1] = { 1 };
std::adjacent_find(std::begin(arr), std::end(arr));
@ -144,6 +146,51 @@ int main(int, char**) {
std::unique(std::begin(arr), std::end(arr), std::greater<int>());
std::upper_bound(std::begin(arr), std::end(arr), 1);
std::upper_bound(std::begin(arr), std::end(arr), 1, std::greater<int>());
}
template<class LV, class RV>
void test_template_cast_wrappers(LV&& lv, RV&& rv) {
std::forward<LV>(lv);
std::forward<RV>(rv);
std::move(lv);
std::move(rv);
std::move_if_noexcept(lv);
std::move_if_noexcept(rv);
#if TEST_STD_VER >= 17
std::as_const(lv);
std::as_const(rv);
#endif
#if TEST_STD_VER >= 20
std::identity()(lv);
std::identity()(rv);
#endif
}
void test_nontemplate_cast_wrappers()
{
#if TEST_STD_VER >= 17
std::byte b{42};
std::to_integer<int>(b);
#endif
#if TEST_STD_VER >= 20
// std::bit_cast<unsigned int>(42);
#endif
#if TEST_STD_VER > 20
enum E { Apple, Orange } e = Apple;
std::to_underlying(e);
#endif
}
int main(int, char**) {
test_algorithms();
int i = 42;
test_template_cast_wrappers(i, std::move(i));
test_nontemplate_cast_wrappers();
return 0;
}

View File

@ -23,9 +23,11 @@
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_NODISCARD
#include <algorithm>
#include <functional>
#include <cstddef> // to_integer
#include <functional> // identity
#include <iterator>
#include <memory>
#include <utility> // to_underlying
#include "test_macros.h"
@ -33,7 +35,7 @@ struct P {
bool operator()(int) const { return false; }
};
int main(int, char**) {
void test_algorithms() {
int arr[1] = { 1 };
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
@ -291,6 +293,63 @@ int main(int, char**) {
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::upper_bound(std::begin(arr), std::end(arr), 1, std::greater<int>());
}
template<class LV, class RV>
void test_template_cast_wrappers(LV&& lv, RV&& rv) {
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::forward<LV>(lv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::forward<RV>(rv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::move(lv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::move(rv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::move_if_noexcept(lv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::move_if_noexcept(rv);
#if TEST_STD_VER >= 17
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::as_const(lv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::as_const(rv);
#endif
#if TEST_STD_VER >= 20
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::identity()(lv);
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::identity()(rv);
#endif
}
void test_nontemplate_cast_wrappers()
{
#if TEST_STD_VER >= 17
std::byte b{42};
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::to_integer<int>(b);
#endif
#if TEST_STD_VER >= 20
// std::bit_cast<unsigned int>(42);
#endif
#if TEST_STD_VER > 20
enum E { Apple, Orange } e = Apple;
// expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}}
std::to_underlying(e);
#endif
}
int main(int, char**) {
test_algorithms();
int i = 42;
test_template_cast_wrappers(i, std::move(i));
test_nontemplate_cast_wrappers();
return 0;
}

View File

@ -23,7 +23,7 @@ int main(int, char**)
{
{
std::forward<A&>(source()); // expected-note {{requested here}}
// expected-error-re@type_traits:* 1 {{static_assert failed{{.*}} "can not forward an rvalue as an lvalue"}}
// expected-error-re@type_traits:* 1 {{static_assert failed{{.*}} "cannot forward an rvalue as an lvalue"}}
}
{
const A ca = A();