folly/folly/functional/test/ApplyTupleTest.cpp

/*
 * 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/functional/ApplyTuple.h>

#include <algorithm>
#include <array>
#include <iostream>
#include <memory>
#include <utility>

#include <folly/Overload.h>
#include <folly/portability/GTest.h>

namespace {

void func(int a, int b, double c) {
  EXPECT_EQ(a, 1);
  EXPECT_EQ(b, 2);
  EXPECT_EQ(c, 3.0);
}

struct Wat {
  void func(int a, int b, double c) { ::func(a, b, c); }

  double retVal(int a, double b) { return a + b; }

  Wat() {}
  Wat(Wat const&) = delete;

  int foo;
};

struct Overloaded {
  int func(int) { return 0; }
  bool func(bool) { return true; }
};

struct Func {
  int operator()() const { return 1; }
};

struct CopyCount {
  CopyCount() {}
  CopyCount(CopyCount const&) { std::cout << "copy count copy ctor\n"; }
};

void anotherFunc(CopyCount const&) {}

std::function<void(int, int, double)> makeFunc() {
  return &func;
}

struct GuardObjBase {
  GuardObjBase(GuardObjBase&&) noexcept {}
  GuardObjBase() {}
  GuardObjBase(GuardObjBase const&) = delete;
  GuardObjBase& operator=(GuardObjBase const&) = delete;
};

template <class F, class Tuple>
struct GuardObj : GuardObjBase {
  explicit GuardObj(F&& f, Tuple&& args)
      : f_(std::forward<F>(f)), args_(std::forward<Tuple>(args)) {}
  GuardObj(GuardObj&& g) noexcept
      : GuardObjBase(std::move(g)),
        f_(std::move(g.f_)),
        args_(std::move(g.args_)) {}

  ~GuardObj() { folly::apply(f_, args_); }

  GuardObj(const GuardObj&) = delete;
  GuardObj& operator=(const GuardObj&) = delete;

 private:
  F f_;
  Tuple args_;
};

template <class F, class... Args>
GuardObj<typename std::decay<F>::type, std::tuple<Args...>> guard(
    F&& f, Args&&... args) {
  return GuardObj<typename std::decay<F>::type, std::tuple<Args...>>(
      std::forward<F>(f), std::tuple<Args...>(std::forward<Args>(args)...));
}

struct Mover {
  Mover() {}
  Mover(Mover&&) noexcept {}
  Mover(const Mover&) = delete;
  Mover& operator=(const Mover&) = delete;
};

void move_only_func(Mover&&) {}

} // namespace

TEST(ApplyTuple, Test) {
  auto argsTuple = std::make_tuple(1, 2, 3.0);
  auto func2 = func;
  folly::apply(func2, argsTuple);
  folly::apply(func, argsTuple);
  folly::apply(func, std::make_tuple(1, 2, 3.0));
  folly::apply(makeFunc(), std::make_tuple(1, 2, 3.0));
  folly::apply(makeFunc(), argsTuple);

  std::unique_ptr<Wat> wat(new Wat);
  folly::apply(&Wat::func, std::make_tuple(wat.get(), 1, 2, 3.0));
  auto argsTuple2 = std::make_tuple(wat.get(), 1, 2, 3.0);
  folly::apply(&Wat::func, argsTuple2);

  EXPECT_EQ(
      10.0, folly::apply(&Wat::retVal, std::make_tuple(wat.get(), 1, 9.0)));

  auto test = guard(func, 1, 2, 3.0);
  CopyCount cpy;
  auto test2 = guard(anotherFunc, cpy);
  auto test3 = guard(anotherFunc, std::cref(cpy));

  Overloaded ovl;
  EXPECT_EQ(
      0,
      folly::apply(
          static_cast<int (Overloaded::*)(int)>(&Overloaded::func),
          std::make_tuple(&ovl, 12)));
  EXPECT_EQ(
      /* do not code-mode to EXPECT_TRUE */ true,
      folly::apply(
          static_cast<bool (Overloaded::*)(bool)>(&Overloaded::func),
          std::make_tuple(&ovl, false)));

  int x = folly::apply(std::plus<int>(), std::make_tuple(12, 12));
  EXPECT_EQ(24, x);

  Mover m;
  folly::apply(
      move_only_func, std::forward_as_tuple(std::forward<Mover>(Mover())));
  const auto tuple3 = std::make_tuple(1, 2, 3.0);
  folly::apply(func, tuple3);
}

TEST(ApplyTuple, Mutable) {
  auto argsTuple = std::make_tuple(1, 2, 3.0);

  folly::apply(
      [](int a, int b, double c) mutable { func(a, b, c); }, argsTuple);
}

TEST(ApplyTuple, ConstOverloads) {
  struct ConstOverloaded {
    ConstOverloaded() {}
    int operator()() { return 101; }
    int operator()() const { return 102; }
  };

  ConstOverloaded covl;

  // call operator()()
  EXPECT_EQ(folly::apply(covl, std::make_tuple()), 101);
  EXPECT_EQ(folly::apply(std::ref(covl), std::make_tuple()), 101);
  EXPECT_EQ(folly::apply(std::move(covl), std::make_tuple()), 101);

  // call operator()() const
  EXPECT_EQ(
      folly::apply(const_cast<ConstOverloaded const&>(covl), std::make_tuple()),
      102);
  EXPECT_EQ(folly::apply(std::cref(covl), std::make_tuple()), 102);
}

TEST(ApplyTuple, RefOverloads) {
  struct RefOverloaded {
    RefOverloaded() {}
    int operator()() & { return 201; }
    int operator()() const& { return 202; }
    int operator()() && { return 203; }
  };

  RefOverloaded rovl;

  // call operator()() &
  EXPECT_EQ(folly::apply(rovl, std::make_tuple()), 201);
  EXPECT_EQ(folly::apply(std::ref(rovl), std::make_tuple()), 201);

  // call operator()() const &
  EXPECT_EQ(
      folly::apply(const_cast<RefOverloaded const&>(rovl), std::make_tuple()),
      202);
  EXPECT_EQ(folly::apply(std::cref(rovl), std::make_tuple()), 202);

  // call operator()() &&
  EXPECT_EQ(folly::apply(std::move(rovl), std::make_tuple()), 203);
}

struct MemberFunc {
  int x;
  int getX() const { return x; }
  void setX(int xx) { x = xx; }
};

TEST(ApplyTuple, MemberFunction) {
  MemberFunc mf;
  mf.x = 123;

  // call getter
  EXPECT_EQ(folly::apply(&MemberFunc::getX, std::make_tuple(&mf)), 123);

  // call setter
  folly::apply(&MemberFunc::setX, std::make_tuple(&mf, 234));
  EXPECT_EQ(mf.x, 234);
  EXPECT_EQ(folly::apply(&MemberFunc::getX, std::make_tuple(&mf)), 234);
}

TEST(ApplyTuple, MemberFunctionWithRefWrapper) {
  MemberFunc mf;
  mf.x = 234;

  EXPECT_EQ(
      folly::apply(&MemberFunc::getX, std::make_tuple(std::ref(mf))), 234);
}

TEST(ApplyTuple, MemberFunctionWithConstPointer) {
  MemberFunc mf;
  mf.x = 234;

  EXPECT_EQ(
      folly::apply(
          &MemberFunc::getX,
          std::make_tuple(const_cast<MemberFunc const*>(&mf))),
      234);
}

TEST(ApplyTuple, MemberFunctionWithSharedPtr) {
  MemberFunc mf;
  mf.x = 234;

  EXPECT_EQ(
      folly::apply(
          &MemberFunc::getX, std::make_tuple(std::make_shared<MemberFunc>(mf))),
      234);
}

TEST(ApplyTuple, MemberFunctionWithUniquePtr) {
  MemberFunc mf;
  mf.x = 234;

  EXPECT_EQ(
      folly::apply(
          &MemberFunc::getX, std::make_tuple(std::make_unique<MemberFunc>(mf))),
      234);
}

TEST(ApplyTuple, Array) {
  folly::apply(func, std::array<int, 3>{{1, 2, 3}});
  folly::apply(func, std::array<double, 3>{{1, 2, 3}});
}

TEST(ApplyTuple, Pair) {
  auto add = [](int x, int y) { return x + y; };

  EXPECT_EQ(folly::apply(add, std::pair<int, int>{1200, 34}), 1234);
}

TEST(ApplyTuple, MultipleTuples) {
  auto add = [](int x, int y, int z) { return x * 100 + y * 10 + z; };

  EXPECT_EQ(123, folly::apply(add, std::make_tuple(1, 2, 3)));
  EXPECT_EQ(
      123,
      folly::apply(
          add, std::tuple_cat(std::make_tuple(1, 2, 3), std::make_tuple())));
  EXPECT_EQ(
      123,
      folly::apply(
          add, std::tuple_cat(std::make_tuple(1, 2), std::make_tuple(3))));
  EXPECT_EQ(
      123,
      folly::apply(
          add, std::tuple_cat(std::make_tuple(1), std::make_tuple(2, 3))));
  EXPECT_EQ(
      123,
      folly::apply(
          add, std::tuple_cat(std::make_tuple(), std::make_tuple(1, 2, 3))));

  EXPECT_EQ(
      123,
      folly::apply(
          add,
          std::tuple_cat(
              std::make_tuple(1, 2, 3), std::make_tuple(), std::make_tuple())));
  EXPECT_EQ(
      123,
      folly::apply(
          add,
          std::tuple_cat(
              std::make_tuple(1), std::make_tuple(2), std::make_tuple(3))));
  EXPECT_EQ(
      123,
      folly::apply(
          add,
          std::tuple_cat(
              std::make_tuple(1), std::make_tuple(), std::make_tuple(2, 3))));
}

TEST(ApplyTuple, UncurryCopyMove) {
  std::string separator = "================================\n";
  auto formatRow = folly::uncurry([=](std::string a, std::string b) {
    // capture separator by copy
    return separator + a + "\n" + b + "\n" + separator;
  });
  auto row = std::make_tuple("hello", "world");
  auto expected = separator + "hello\nworld\n" + separator;
  EXPECT_EQ(expected, formatRow(row));
  auto formatRowCopy = formatRow;
  EXPECT_EQ(expected, formatRowCopy(row));
  auto formatRowMove = std::move(formatRow);
  EXPECT_EQ(expected, formatRowMove(row));

  // capture value moved out from formatRow
  EXPECT_NE(expected, formatRow(row));
}

TEST(ApplyTuple, Uncurry) {
  EXPECT_EQ(42, folly::uncurry([](int x, int y) {
              return x * y;
            })(std::pair<int, int>(6, 7)));
  EXPECT_EQ(42, folly::uncurry([](int&& x, int&& y) {
              return x * y;
            })(std::pair<int&&, int&&>(6, 7)));
  EXPECT_EQ(42, folly::uncurry([](int&& x, int&& y) {
              return x * y;
            })(std::pair<int&&, int&&>(6, 7)));

  std::string long1 = "a long string exceeding small string size";
  std::string long2 = "and here is another one!";
  std::string expected = long1 + long2;

  auto cat = folly::uncurry(
      [](std::string a, std::string b) { return std::move(a) + std::move(b); });

  EXPECT_EQ(expected, cat(std::make_pair(long1, long2)));
  EXPECT_FALSE(long1.empty());
  EXPECT_FALSE(long2.empty());
  EXPECT_EQ(expected, cat(std::tie(long1, long2)));
  EXPECT_FALSE(long1.empty());
  EXPECT_FALSE(long2.empty());
  EXPECT_EQ(
      expected, cat(std::forward_as_tuple(std::move(long1), std::move(long2))));
  EXPECT_TRUE(long1.empty());
  EXPECT_TRUE(long2.empty());
}

TEST(ApplyTuple, UncurryStdFind) {
  std::vector<std::pair<int, int>> v{{1, 9}, {2, 8}, {3, 7}, {4, 6}, {5, 5}};
  EXPECT_EQ(
      3, std::count_if(v.begin(), v.end(), folly::uncurry([](int a, int b) {
                         return b % a == 0;
                       })));
}

namespace {
struct S {
  template <typename... Args>
  explicit S(Args&&... args) : tuple_(std::forward<Args>(args)...) {}

  std::tuple<int, double, std::string> tuple_;
};
} // namespace

TEST(MakeIndexSequenceFromTuple, Basic) {
  using folly::index_sequence_for_tuple;
  using OneElementTuple = std::tuple<int>;
  using TwoElementTuple = std::tuple<int>;

  EXPECT_TRUE((std::is_same<
               index_sequence_for_tuple<OneElementTuple>,
               std::index_sequence<0>>::value));
  EXPECT_TRUE((std::is_same<
               index_sequence_for_tuple<const OneElementTuple>,
               std::index_sequence<0>>::value));

  EXPECT_TRUE((std::is_same<
               index_sequence_for_tuple<TwoElementTuple>,
               std::index_sequence<0>>::value));
  EXPECT_TRUE((std::is_same<
               index_sequence_for_tuple<const TwoElementTuple>,
               std::index_sequence<0>>::value));
}

TEST(ApplyResult, Basic) {
  {
    auto f = [](auto) -> int { return {}; };
    using F = decltype(f);
    EXPECT_TRUE(
        (std::is_same<folly::apply_result_t<F, std::tuple<int>>, int>{}));
  }

  {
    auto f = folly::overload(
        [](int) {},
        [](double) -> double { return {}; },
        [](int, int) -> int { return {}; });
    using F = decltype(f);

    EXPECT_TRUE(
        (std::is_same<folly::apply_result_t<F, std::tuple<int>>, void>::value));
    EXPECT_TRUE(
        (std::is_same<folly::apply_result_t<F, std::tuple<double>>, double>::
             value));
    EXPECT_TRUE(
        (std::is_same<folly::apply_result_t<F, std::tuple<int, int>>, int>::
             value));
  }
}

TEST(IsApplicable, Basic) {
  {
    auto f = [] {};
    using F = decltype(f);
    EXPECT_TRUE((folly::is_applicable_v<F, std::tuple<>>));
    EXPECT_FALSE((folly::is_applicable_v<F, std::tuple<int>>));
  }
  {
    auto f = folly::overload([](int) {}, [](double) -> double { return {}; });
    using F = decltype(f);
    EXPECT_TRUE((folly::is_applicable_v<F, std::tuple<double>>));
    EXPECT_TRUE((folly::is_applicable_v<F, std::tuple<int>>));
    EXPECT_FALSE((folly::is_applicable_v<F, std::tuple<>>));
    EXPECT_FALSE((folly::is_applicable_v<F, std::tuple<int, double>>));
  }
}

TEST(IsNothrowApplicable, Basic) {
  {
    auto f = []() noexcept {};
    using F = decltype(f);
    EXPECT_TRUE((folly::is_nothrow_applicable_v<F, std::tuple<>>));
    EXPECT_FALSE((folly::is_nothrow_applicable_v<F, std::tuple<int>>));
  }
  {
    auto f = folly::overload(
        [](int) noexcept {}, [](double) -> double { return {}; });
    using F = decltype(f);
    EXPECT_FALSE((folly::is_nothrow_applicable_v<F, std::tuple<double>>));
    EXPECT_TRUE((folly::is_nothrow_applicable_v<F, std::tuple<int>>));
    EXPECT_FALSE((folly::is_nothrow_applicable_v<F, std::tuple<>>));
    EXPECT_FALSE((folly::is_nothrow_applicable_v<F, std::tuple<int, double>>));
  }
}

TEST(IsApplicableR, Basic) {
  {
    auto f = []() -> int { return {}; };
    using F = decltype(f);
    EXPECT_TRUE((folly::is_applicable_r_v<double, F, std::tuple<>>));
    EXPECT_FALSE((folly::is_applicable_r_v<double, F, std::tuple<int>>));
  }
  {
    auto f = folly::overload(
        [](int) noexcept {}, [](double) -> double { return {}; });
    using F = decltype(f);
    EXPECT_TRUE((folly::is_applicable_r_v<float, F, std::tuple<double>>));
    EXPECT_TRUE((folly::is_applicable_r_v<void, F, std::tuple<int>>));
    EXPECT_FALSE((folly::is_applicable_r_v<void, F, std::tuple<>>));
    EXPECT_FALSE(
        (folly::is_applicable_r_v<double, F, std::tuple<int, double>>));
  }
}

TEST(IsNothrowApplicableR, Basic) {
  {
    auto f = []() noexcept -> int { return {}; };
    using F = decltype(f);
    EXPECT_TRUE((folly::is_nothrow_applicable_r_v<double, F, std::tuple<>>));
    EXPECT_FALSE(
        (folly::is_nothrow_applicable_r_v<double, F, std::tuple<int>>));
  }
  {
    auto f = folly::overload(
        [](int) noexcept {}, [](double) -> double { return {}; });
    using F = decltype(f);
    EXPECT_FALSE(
        (folly::is_nothrow_applicable_r_v<float, F, std::tuple<double>>));
    EXPECT_TRUE((folly::is_nothrow_applicable_r_v<void, F, std::tuple<int>>));
    EXPECT_FALSE((folly::is_nothrow_applicable_r_v<void, F, std::tuple<>>));
    EXPECT_FALSE(
        (folly::is_nothrow_applicable_r_v<double, F, std::tuple<int, double>>));
  }
}

TEST(ForwardTuple, Basic) {
  auto tuple = std::make_tuple(1, 2.0);

  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(tuple)),
               std::tuple<int&, double&>>::value));
  EXPECT_EQ(folly::forward_tuple(tuple), tuple);
  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(std::as_const(tuple))),
               std::tuple<const int&, const double&>>::value));
  EXPECT_EQ(folly::forward_tuple(std::as_const(tuple)), tuple);

  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(std::move(tuple))),
               std::tuple<int&&, double&&>>::value));
  EXPECT_EQ(folly::forward_tuple(std::move(tuple)), tuple);
#if defined(__GLIBCXX__) && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE < 8)
  constexpr bool before_lwg2485 = true;
#else
  constexpr bool before_lwg2485 = false;
#endif
  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(std::move(std::as_const(tuple)))),
               std::conditional_t<
                   before_lwg2485,
                   std::tuple<const int&, const double&>,
                   std::tuple<const int&&, const double&&>>>::value));
  EXPECT_EQ(folly::forward_tuple(std::move(std::as_const(tuple))), tuple);

  auto integer = 1;
  auto floating_point = 2.0;
  auto ref_tuple = std::forward_as_tuple(integer, std::move(floating_point));

  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(ref_tuple)),
               std::tuple<int&, double&>>::value));

  EXPECT_TRUE((std::is_same<
               decltype(folly::forward_tuple(std::move(ref_tuple))),
               std::tuple<int&, double&&>>::value));

  EXPECT_TRUE((std::is_same<
               decltype(std::tuple_cat(
                   folly::forward_tuple(tuple),
                   folly::forward_tuple(std::move(tuple)))),
               std::tuple<int&, double&, int&&, double&&>>::value));
}