/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Utility.h>
#include <type_traits>
#include <folly/lang/Keep.h>
#include <folly/portability/GTest.h>
namespace folly {
extern "C" FOLLY_KEEP int check_unsafe_default_initialized_int_ret() {
int a = folly::unsafe_default_initialized;
return a;
}
extern "C" FOLLY_KEEP void check_unsafe_default_initialized_int_set(int* p) {
int a = folly::unsafe_default_initialized;
*p = a;
}
extern "C" FOLLY_KEEP void check_unsafe_default_initialized_int_pass() {
int a = folly::unsafe_default_initialized;
folly::detail::keep_sink_nx(a);
}
extern "C" FOLLY_KEEP int check_unsafe_default_initialized_int_cexpr() {
constexpr int a = folly::unsafe_default_initialized;
return a;
}
// for ADL test below.
template <class T>
int square(const T& x) {
return x * x;
}
} // namespace folly
class UtilityTest : public testing::Test {};
namespace my_type {
struct MoveInt : folly::MoveOnly {
int x = 0;
};
struct NoMoveInt : folly::NonCopyableNonMovable {
int x = 0;
};
template <class M>
int square(const M& m) {
return m.x * m.x;
}
} // namespace my_type
TEST_F(UtilityTest, NonCopyableAggregateInit) {
// Ensure classes inheriting from folly::MoveOnly, etc, do not find all of
// folly::* by ADL.
EXPECT_EQ(16, square(my_type::MoveInt{.x = 4}));
EXPECT_EQ(25, square(my_type::NoMoveInt{.x = 5}));
using folly::square;
EXPECT_EQ(36, square(6));
// Ambiguous: EXPECT_EQ(16, square(my_type::MoveInt{.x = 4}));
}
// Tests for FOLLY_DECLVAL macro:
static_assert(std::is_same_v<decltype(FOLLY_DECLVAL(int)), int>);
static_assert(std::is_same_v<decltype(FOLLY_DECLVAL(int&)), int&>);
static_assert(std::is_same_v<decltype(FOLLY_DECLVAL(int&&)), int&&>);
static_assert(noexcept(FOLLY_DECLVAL(int)));
// Tests for folly::decay_t:
template <typename T>
using dec = folly::decay_t<T>;
struct incomplete;
struct abstract {
virtual ~abstract() = 0;
};
struct immobile {
immobile() = delete;
immobile(immobile&&) = delete;
void operator=(immobile&&) = delete;
~immobile() = delete;
};
static_assert(std::is_same_v<int, dec<int>>);
static_assert(std::is_same_v<int, dec<int&>>);
static_assert(std::is_same_v<int, dec<int&&>>);
static_assert(std::is_same_v<int, dec<int const>>);
static_assert(std::is_same_v<int, dec<int const&&>>);
static_assert(std::is_same_v<int, dec<int const&>>);
static_assert(std::is_same_v<int, dec<int volatile>>);
static_assert(std::is_same_v<int, dec<int volatile&>>);
static_assert(std::is_same_v<int, dec<int volatile&&>>);
static_assert(std::is_same_v<int, dec<int const volatile>>);
static_assert(std::is_same_v<int, dec<int const volatile&>>);
static_assert(std::is_same_v<int, dec<int const volatile&&>>);
static_assert(std::is_same_v<int*, dec<int*>>);
static_assert(std::is_same_v<int*, dec<int[]>>);
static_assert(std::is_same_v<int*, dec<int[7]>>);
static_assert(std::is_same_v<int*, dec<int*&>>);
static_assert(std::is_same_v<int*, dec<int (&)[]>>);
static_assert(std::is_same_v<int*, dec<int (&)[7]>>);
static_assert(std::is_same_v<int const*, dec<int const*>>);
static_assert(std::is_same_v<int const*, dec<int const[]>>);
static_assert(std::is_same_v<int const*, dec<int const[7]>>);
static_assert(std::is_same_v<int const*, dec<int const*&>>);
static_assert(std::is_same_v<int const*, dec<int const (&)[]>>);
static_assert(std::is_same_v<int const*, dec<int const (&)[7]>>);
static_assert(std::is_same_v<int (*)(), dec<int (*)()>>);
static_assert(std::is_same_v<int (*)() noexcept, dec<int (*)() noexcept>>);
static_assert(std::is_same_v<int (*)(), dec<int (&)()>>);
static_assert(std::is_same_v<int (*)() noexcept, dec<int (&)() noexcept>>);
static_assert(std::is_same_v<void, dec<void>>);
static_assert(std::is_same_v<void, dec<void const>>);
static_assert(std::is_same_v<void, dec<void volatile>>);
static_assert(std::is_same_v<void, dec<void const volatile>>);
static_assert(std::is_same_v<incomplete, dec<incomplete>>);
static_assert(std::is_same_v<incomplete, dec<incomplete const>>);
static_assert(std::is_same_v<abstract, dec<abstract>>);
static_assert(std::is_same_v<abstract, dec<abstract const>>);
static_assert(std::is_same_v<immobile, dec<immobile>>);
static_assert(std::is_same_v<immobile, dec<immobile const>>);
TEST_F(UtilityTest, copy) {
struct MyData {};
struct Worker {
size_t rrefs = 0, crefs = 0;
void something(MyData&&) { ++rrefs; }
void something(const MyData&) { ++crefs; }
};
MyData data;
Worker worker;
worker.something(folly::copy(data));
worker.something(std::move(data));
worker.something(data);
EXPECT_EQ(2, worker.rrefs);
EXPECT_EQ(1, worker.crefs);
}
TEST_F(UtilityTest, copy_noexcept_spec) {
struct MyNoexceptCopyable {};
MyNoexceptCopyable noe;
EXPECT_TRUE(noexcept(folly::copy(noe)));
EXPECT_TRUE(noexcept(folly::copy(std::move(noe))));
struct MyThrowingCopyable {
MyThrowingCopyable() {}
MyThrowingCopyable(const MyThrowingCopyable&) noexcept(false) {}
MyThrowingCopyable(MyThrowingCopyable&&) = default;
};
MyThrowingCopyable thr;
EXPECT_FALSE(noexcept(folly::copy(thr)));
EXPECT_TRUE(noexcept(folly::copy(std::move(thr)))); // note: does not copy
}
template <typename T>
static T& as_mutable(T const& t) {
return const_cast<T&>(t);
}
TEST_F(UtilityTest, forward_like) {
int x = 0;
// just show that it may be invoked, and that it is purely a cast
// the real work is done by like_t, in terms of which forward_like is defined
EXPECT_EQ(&x, std::addressof(folly::forward_like<char&>(x)));
EXPECT_EQ(&x, std::addressof(as_mutable(folly::forward_like<char const>(x))));
// Should not be able to turn rvalues into lvalues
// Uncomment to produce expected compile-time errors
// std::forward<const int&>(1);
// folly::forward_like<const int&>(1);
}
TEST_F(UtilityTest, literal_string) {
constexpr auto s = folly::literal_string{"hello world"};
EXPECT_STREQ("hello world", s.c_str());
EXPECT_EQ(s.c_str(), s.data());
EXPECT_EQ(strlen(s.c_str()), s.size());
EXPECT_EQ(s.c_str(), std::string_view{s});
EXPECT_EQ(s.c_str(), std::string{s});
}
#if FOLLY_CPLUSPLUS >= 202002
TEST_F(UtilityTest, literal_string_op_lit) {
using namespace folly::string_literals;
constexpr auto s = "hello world"_lit;
EXPECT_STREQ("hello world", s.c_str());
EXPECT_EQ(s.c_str(), s.data());
EXPECT_EQ(strlen(s.c_str()), s.size());
EXPECT_EQ(s.c_str(), std::string_view{s});
EXPECT_EQ(s.c_str(), std::string{s});
}
TEST_F(UtilityTest, literal_string_op_litv) {
using namespace folly::string_literals;
using t = decltype("hello world"_litv);
constexpr auto s = folly::value_list_element_v<0, t>;
EXPECT_STREQ("hello world", s.c_str());
EXPECT_EQ(s.c_str(), s.data());
EXPECT_EQ(strlen(s.c_str()), s.size());
EXPECT_EQ(s.c_str(), std::string_view{s});
EXPECT_EQ(s.c_str(), std::string{s});
}
#endif
TEST_F(UtilityTest, inheritable) {
struct foo : folly::index_constant<47> {};
struct bar final : folly::index_constant<89> {};
using ifoo = folly::detail::inheritable<foo>;
using ibar = folly::detail::inheritable<bar>;
EXPECT_TRUE(std::is_empty_v<ifoo>);
EXPECT_FALSE(std::is_empty_v<ibar>);
struct tester : ifoo, ibar {};
EXPECT_EQ(47, static_cast<foo const&>(tester{}));
EXPECT_EQ(89, static_cast<bar const&>(tester{}));
EXPECT_EQ(47, static_cast<foo const&>(folly::copy(tester{})));
EXPECT_EQ(89, static_cast<bar const&>(folly::copy(tester{})));
}
template <bool Copy, bool Move>
class TestCopyMove {
using T = folly::moveonly_::EnableCopyMove<Copy, Move>;
static_assert(std::is_copy_constructible_v<T> == Copy);
// Allowing copy but disallowing move is not effective from a base class;
// calls to move the child class will just resolve to copy.
static_assert(std::is_move_constructible_v<T> == (Move || Copy));
};
template class TestCopyMove<false, false>;
template class TestCopyMove<false, true>;
template class TestCopyMove<true, false>;
template class TestCopyMove<true, true>;
TEST_F(UtilityTest, MoveOnly) {
class FooBar : folly::MoveOnly {
int a;
};
static_assert(
!std::is_copy_constructible<FooBar>::value,
"Should not be copy constructible");
// Test that move actually works.
FooBar foobar;
FooBar foobar2(std::move(foobar));
(void)foobar2;
// Test that inheriting from MoveOnly doesn't prevent the move
// constructor from being noexcept.
static_assert(
std::is_nothrow_move_constructible<FooBar>::value,
"Should have noexcept move constructor");
}
TEST_F(UtilityTest, NonCopyableNonMovable) {
class FooBar : folly::NonCopyableNonMovable {
int a;
};
static_assert(
!std::is_copy_constructible<FooBar>::value,
"Should not be copy constructible");
static_assert(
!std::is_move_constructible<FooBar>::value,
"Should not be move constructible");
}
TEST_F(UtilityTest, noop) {
auto fn = folly::variadic_noop;
EXPECT_TRUE((std::is_nothrow_invocable_r_v<void, decltype(fn)>));
}
TEST_F(UtilityTest, constant_of) {
auto fn = folly::variadic_constant_of<3>;
EXPECT_TRUE((std::is_nothrow_invocable_r_v<int, decltype(fn)>));
EXPECT_EQ(3, fn());
}
TEST_F(UtilityTest, to_signed) {
{
constexpr auto actual = folly::to_signed(int32_t(-12));
EXPECT_TRUE(std::is_signed<decltype(actual)>::value);
EXPECT_EQ(-12, actual);
}
{
constexpr auto actual = folly::to_signed(uint32_t(-12));
EXPECT_TRUE(std::is_signed<decltype(actual)>::value);
EXPECT_EQ(-12, actual);
}
}
TEST_F(UtilityTest, to_unsigned) {
{
constexpr auto actual = folly::to_unsigned(int32_t(-12));
EXPECT_TRUE(!std::is_signed<decltype(actual)>::value);
EXPECT_EQ(-12, actual);
}
{
constexpr auto actual = folly::to_unsigned(uint32_t(-12));
EXPECT_TRUE(!std::is_signed<decltype(actual)>::value);
EXPECT_EQ(-12, actual);
}
}
TEST_F(UtilityTest, to_narrow) {
{
constexpr uint32_t actual = folly::to_narrow(uint64_t(100));
EXPECT_EQ(100, actual);
}
}
TEST_F(UtilityTest, to_integral) {
{
constexpr uint32_t actual = folly::to_integral(100.0f);
EXPECT_EQ(100, actual);
}
}
TEST_F(UtilityTest, to_floating_point) {
{
constexpr float actual = folly::to_floating_point(100);
EXPECT_EQ(100.f, actual);
}
}
TEST_F(UtilityTest, invocable_to_basic) {
struct foo {
int value = 0;
explicit foo(int value_) noexcept : value{value_} {}
foo(foo const&) = delete;
void operator=(foo const&) = delete;
};
foo f = folly::invocable_to([] { return foo(17); });
EXPECT_EQ(17, f.value);
}
namespace folly::detail::invocable_to_test {
template <typename T, bool C, bool R, bool X>
struct fun;
template <typename T, bool X>
struct fun<T, 0, 0, X> {
constexpr T operator()() & noexcept(X);
};
template <typename T, bool X>
struct fun<T, 0, 1, X> {
constexpr T operator()() && noexcept(X);
};
template <typename T, bool X>
struct fun<T, 1, 0, X> {
constexpr T operator()() const& noexcept(X);
};
template <typename T, bool X>
struct fun<T, 1, 1, X> {
constexpr T operator()() const&& noexcept(X);
};
template <typename T, bool C, bool R, bool X>
using of = std::invoke_result_t<folly::invocable_to_fn, fun<T, C, R, X>>;
template <typename S, typename D>
static constexpr bool is_conv_v = std::is_convertible_v<S, D>;
template <typename S, typename D>
static constexpr bool is_nx_conv_v = //
is_conv_v<S, D> && std::is_nothrow_constructible_v<D, S>;
static_assert(is_conv_v<of<int, 0, 0, 0>&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 0> const&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 0>&&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 0> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 0>&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 0> const&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 0>&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 0> const&&, int>);
static_assert(is_conv_v<of<int, 0, 0, 1>&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 1> const&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 1>&&, int>);
static_assert(!is_conv_v<of<int, 0, 0, 1> const&&, int>);
static_assert(is_nx_conv_v<of<int, 0, 0, 1>&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 1> const&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 1>&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 0, 1> const&&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 0>&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 0> const&, int>);
static_assert(is_conv_v<of<int, 0, 1, 0>&&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 0> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 0>&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 0> const&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 0>&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 0> const&&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 1>&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 1> const&, int>);
static_assert(is_conv_v<of<int, 0, 1, 1>&&, int>);
static_assert(!is_conv_v<of<int, 0, 1, 1> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 1>&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 1> const&, int>);
static_assert(is_nx_conv_v<of<int, 0, 1, 1>&&, int>);
static_assert(!is_nx_conv_v<of<int, 0, 1, 1> const&&, int>);
static_assert(is_conv_v<of<int, 1, 0, 0>&, int>);
static_assert(is_conv_v<of<int, 1, 0, 0> const&, int>);
static_assert(is_conv_v<of<int, 1, 0, 0>&&, int>);
static_assert(is_conv_v<of<int, 1, 0, 0> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 0, 0>&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 0, 0> const&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 0, 0>&&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 0, 0> const&&, int>);
static_assert(is_conv_v<of<int, 1, 0, 1>&, int>);
static_assert(is_conv_v<of<int, 1, 0, 1> const&, int>);
static_assert(is_conv_v<of<int, 1, 0, 1>&&, int>);
static_assert(is_conv_v<of<int, 1, 0, 1> const&&, int>);
static_assert(is_nx_conv_v<of<int, 1, 0, 1>&, int>);
static_assert(is_nx_conv_v<of<int, 1, 0, 1> const&, int>);
static_assert(is_nx_conv_v<of<int, 1, 0, 1>&&, int>);
static_assert(is_nx_conv_v<of<int, 1, 0, 1> const&&, int>);
static_assert(!is_conv_v<of<int, 1, 1, 0>&, int>);
static_assert(!is_conv_v<of<int, 1, 1, 0> const&, int>);
static_assert(is_conv_v<of<int, 1, 1, 0>&&, int>);
static_assert(is_conv_v<of<int, 1, 1, 0> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 0>&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 0> const&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 0>&&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 0> const&&, int>);
static_assert(!is_conv_v<of<int, 1, 1, 1>&, int>);
static_assert(!is_conv_v<of<int, 1, 1, 1> const&, int>);
static_assert(is_conv_v<of<int, 1, 1, 1>&&, int>);
static_assert(is_conv_v<of<int, 1, 1, 1> const&&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 1>&, int>);
static_assert(!is_nx_conv_v<of<int, 1, 1, 1> const&, int>);
static_assert(is_nx_conv_v<of<int, 1, 1, 1>&&, int>);
static_assert(is_nx_conv_v<of<int, 1, 1, 1> const&&, int>);
} // namespace folly::detail::invocable_to_test