Add --gen-absl-hash option to generate AbslHashValue for structs. (#8868)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7c835c..9e39c49 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -571,8 +571,6 @@
 
   # Since flatsample has no sources, we have to explicitly set the linker lang.
   set_target_properties(flatsample PROPERTIES LINKER_LANGUAGE CXX)
-  
-  compile_schema_for_samples(samples/monster.fbs "${FLATC_OPT_COMP}")
 
   target_link_libraries(flatsamplebinary PRIVATE $<BUILD_INTERFACE:ProjectConfig> flatsample)
   target_link_libraries(flatsampletext PRIVATE $<BUILD_INTERFACE:ProjectConfig> flatsample)
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 4dda9ba..3e35b48 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -664,6 +664,7 @@
   bool generate_name_strings;
   bool generate_object_based_api;
   bool gen_compare;
+  bool gen_absl_hash;
   std::string cpp_object_api_pointer_type;
   std::string cpp_object_api_string_type;
   bool cpp_object_api_string_flexible_constructor;
@@ -818,6 +819,7 @@
         generate_name_strings(false),
         generate_object_based_api(false),
         gen_compare(false),
+        gen_absl_hash(false),
         cpp_object_api_pointer_type("std::unique_ptr"),
         cpp_object_api_string_flexible_constructor(false),
         cpp_object_api_field_case_style(CaseStyle_Unchanged),
diff --git a/samples/monster_generated.h b/samples/monster_generated.h
index a0facac..a618cbf 100644
--- a/samples/monster_generated.h
+++ b/samples/monster_generated.h
@@ -236,6 +236,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Vec3 &obj) {
+  return H::combine(std::move(h), obj.x(), obj.y(), obj.z());
+}
 
 struct MonsterT : public ::flatbuffers::NativeTable {
   typedef Monster TableType;
diff --git a/scripts/generate_code.py b/scripts/generate_code.py
index 68b9869..09ebf88 100755
--- a/scripts/generate_code.py
+++ b/scripts/generate_code.py
@@ -69,6 +69,7 @@
 CPP_OPTS = [
     "--cpp",
     "--gen-compare",
+    "--gen-absl-hash",
 ] + (["--cpp-std", "c++0x"] if args.cpp_0x else [])
 
 CPP_17_OPTS = NO_INCL_OPTS + [
diff --git a/src/flatc.cpp b/src/flatc.cpp
index 761af1e..4bb590b 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -529,6 +529,8 @@
         opts.generate_object_based_api = true;
       } else if (arg == "--gen-compare") {
         opts.gen_compare = true;
+      } else if (arg == "--gen-absl-hash") {
+        opts.gen_absl_hash = true;
       } else if (arg == "--cpp-include") {
         if (++argi >= argc) Error("missing include following: " + arg, true);
         opts.cpp_includes.push_back(argv[argi]);
diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp
index f056727..54706b0 100644
--- a/src/idl_gen_cpp.cpp
+++ b/src/idl_gen_cpp.cpp
@@ -2311,6 +2311,34 @@
     code_ += "";
   }
 
+  void GenAbslHashValue(const StructDef& struct_def) {
+    code_ += "template <typename H>";
+    code_ += "inline H AbslHashValue(H h, const {{NATIVE_NAME}} &obj) {";
+    std::string combine_args;
+    for (const auto& field : struct_def.fields.vec) {
+      if (field->deprecated) {
+        continue;
+      }
+      if (IsArray(field->value.type)) {
+        const auto field_accessor = "obj." + Name(*field) + "()";
+        const auto& element_type = field->value.type.VectorType();
+        if (IsStruct(element_type)) {
+          code_ += "  for (const auto* element : *" + field_accessor + ") {";
+          code_ += "    h = H::combine(std::move(h), *element);";
+          code_ += "  }";
+        } else {
+          code_ += "  for (auto element : *" + field_accessor + ") {";
+          code_ += "    h = H::combine(std::move(h), element);";
+          code_ += "  }";
+        }
+      } else {
+        combine_args += ", obj." + Name(*field) + "()";
+      }
+    }
+    code_ += "  return H::combine(std::move(h)" + combine_args + ");";
+    code_ += "}";
+  }
+
   void GenOperatorNewDelete(const StructDef& struct_def) {
     if (auto native_custom_alloc =
             struct_def.attributes.Lookup("native_custom_alloc")) {
@@ -4281,7 +4309,12 @@
 
     code_.SetValue("STRUCT_BYTE_SIZE", NumToString(struct_def.bytesize));
     code_ += "FLATBUFFERS_STRUCT_END({{STRUCT_NAME}}, {{STRUCT_BYTE_SIZE}});";
-    if (opts_.gen_compare) GenCompareOperator(struct_def, "()");
+    if (opts_.gen_compare) {
+      GenCompareOperator(struct_def, "()");
+    }
+    if (opts_.gen_absl_hash) {
+      GenAbslHashValue(struct_def);
+    }
     code_ += "";
 
     // Definition for type traits for this table type. This allows querying var-
diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel
index 9aecdf7..d95eac8 100644
--- a/tests/BUILD.bazel
+++ b/tests/BUILD.bazel
@@ -213,6 +213,7 @@
 include_test_args = [
     "--gen-object-api",
     "--gen-compare",
+    "--gen-absl-hash",
     "--gen-mutable",
     "--reflect-names",
     "--cpp-ptr-type flatbuffers::unique_ptr",
diff --git a/tests/evolution_test/evolution_v1_generated.h b/tests/evolution_test/evolution_v1_generated.h
index 827d058..349048c 100644
--- a/tests/evolution_test/evolution_v1_generated.h
+++ b/tests/evolution_test/evolution_v1_generated.h
@@ -145,6 +145,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Struct &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 struct TableA FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
   typedef TableABuilder Builder;
diff --git a/tests/evolution_test/evolution_v2_generated.h b/tests/evolution_test/evolution_v2_generated.h
index 885830d..baa678f 100644
--- a/tests/evolution_test/evolution_v2_generated.h
+++ b/tests/evolution_test/evolution_v2_generated.h
@@ -161,6 +161,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Struct &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 struct TableA FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
   typedef TableABuilder Builder;
diff --git a/tests/monster_test_generated.h b/tests/monster_test_generated.h
index 594670b..81893af 100644
--- a/tests/monster_test_generated.h
+++ b/tests/monster_test_generated.h
@@ -712,6 +712,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Test &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
  private:
@@ -810,6 +814,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Vec3 &obj) {
+  return H::combine(std::move(h), obj.x(), obj.y(), obj.z(), obj.test1(), obj.test2(), obj.test3());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
  private:
@@ -859,6 +867,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Ability &obj) {
+  return H::combine(std::move(h), obj.id(), obj.distance());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -912,6 +924,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructs &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b(), obj.c());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructsOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -945,6 +961,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructsOfStructs &obj) {
+  return H::combine(std::move(h), obj.a());
+}
 
 }  // namespace Example
 
diff --git a/tests/monster_test_suffix/ext_only/monster_test_generated.hpp b/tests/monster_test_suffix/ext_only/monster_test_generated.hpp
index b3db30d..b6bf2e9 100644
--- a/tests/monster_test_suffix/ext_only/monster_test_generated.hpp
+++ b/tests/monster_test_suffix/ext_only/monster_test_generated.hpp
@@ -709,6 +709,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Test &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
  private:
@@ -807,6 +811,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Vec3 &obj) {
+  return H::combine(std::move(h), obj.x(), obj.y(), obj.z(), obj.test1(), obj.test2(), obj.test3());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
  private:
@@ -856,6 +864,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Ability &obj) {
+  return H::combine(std::move(h), obj.id(), obj.distance());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -909,6 +921,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructs &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b(), obj.c());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructsOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -942,6 +958,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructsOfStructs &obj) {
+  return H::combine(std::move(h), obj.a());
+}
 
 }  // namespace Example
 
diff --git a/tests/monster_test_suffix/filesuffix_only/monster_test_suffix.h b/tests/monster_test_suffix/filesuffix_only/monster_test_suffix.h
index b3db30d..b6bf2e9 100644
--- a/tests/monster_test_suffix/filesuffix_only/monster_test_suffix.h
+++ b/tests/monster_test_suffix/filesuffix_only/monster_test_suffix.h
@@ -709,6 +709,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Test &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
  private:
@@ -807,6 +811,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Vec3 &obj) {
+  return H::combine(std::move(h), obj.x(), obj.y(), obj.z(), obj.test1(), obj.test2(), obj.test3());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
  private:
@@ -856,6 +864,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Ability &obj) {
+  return H::combine(std::move(h), obj.id(), obj.distance());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -909,6 +921,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructs &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b(), obj.c());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructsOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -942,6 +958,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructsOfStructs &obj) {
+  return H::combine(std::move(h), obj.a());
+}
 
 }  // namespace Example
 
diff --git a/tests/monster_test_suffix/monster_test_suffix.hpp b/tests/monster_test_suffix/monster_test_suffix.hpp
index b3db30d..b6bf2e9 100644
--- a/tests/monster_test_suffix/monster_test_suffix.hpp
+++ b/tests/monster_test_suffix/monster_test_suffix.hpp
@@ -709,6 +709,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Test &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Vec3 FLATBUFFERS_FINAL_CLASS {
  private:
@@ -807,6 +811,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Vec3 &obj) {
+  return H::combine(std::move(h), obj.x(), obj.y(), obj.z(), obj.test1(), obj.test2(), obj.test3());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Ability FLATBUFFERS_FINAL_CLASS {
  private:
@@ -856,6 +864,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Ability &obj) {
+  return H::combine(std::move(h), obj.id(), obj.distance());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -909,6 +921,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructs &obj) {
+  return H::combine(std::move(h), obj.a(), obj.b(), obj.c());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) StructOfStructsOfStructs FLATBUFFERS_FINAL_CLASS {
  private:
@@ -942,6 +958,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const StructOfStructsOfStructs &obj) {
+  return H::combine(std::move(h), obj.a());
+}
 
 }  // namespace Example
 
diff --git a/tests/test.cpp b/tests/test.cpp
index ccd4527..05e2f33 100644
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -26,6 +26,13 @@
 #define INCLUDE_64_BIT_TESTS 1
 #endif
 
+#if __has_include("third_party/absl/container/flat_hash_set.h")
+#define HAS_ABSL_CONTAINERS 1
+#endif
+
+#ifdef HAS_ABSL_CONTAINERS
+#include "third_party/absl/container/flat_hash_set.h"
+#endif
 #include "alignment_test.h"
 #include "evolution_test.h"
 #include "flatbuffers/flatbuffers.h"
@@ -1665,6 +1672,40 @@
   TEST_ASSERT(unpacked.test_vector_of_union == buffer.test_vector_of_union);
 }
 
+void StructsInHashTableTest() {
+#if defined(HAS_ABSL_CONTAINERS) && (!defined(_MSC_VER) || _MSC_VER >= 1700)
+  absl::flat_hash_set<ArrayStruct> hash_set;
+  ArrayStruct array_struct_1;
+  array_struct_1.mutate_a(0.4);
+  for (int i = 0; i < array_struct_1.b()->size(); ++i) {
+    array_struct_1.mutable_b()->Mutate(i, i * 2);
+  }
+  for (int i = 0; i < array_struct_1.d()->size(); ++i) {
+    NestedStruct nested_struct;
+    nested_struct.mutable_a()->Mutate(0, i * 3);
+    array_struct_1.mutable_d()->Mutate(i, nested_struct);
+  }
+
+  ArrayStruct array_struct_2;
+  array_struct_2.mutate_e(999);
+
+  hash_set.insert(array_struct_1);
+  hash_set.insert(array_struct_2);
+
+  TEST_EQ(hash_set.size(), 2);
+  TEST_ASSERT(hash_set.contains(array_struct_1));
+  TEST_ASSERT(hash_set.contains(array_struct_2));
+
+  ArrayStruct array_struct_3 = array_struct_1;
+  array_struct_3.mutable_b()->Mutate(0, 2);
+  TEST_ASSERT(!hash_set.contains(array_struct_3));
+
+  hash_set.insert(array_struct_3);
+  TEST_ASSERT(hash_set.contains(array_struct_3));
+#endif  // defined(HAS_ABSL_CONTAINERS) && (!defined(_MSC_VER) || _MSC_VER >=
+        // 1700)
+}
+
 static void Offset64Tests() {
 #if INCLUDE_64_BIT_TESTS
   Offset64Test();
@@ -1794,6 +1835,7 @@
   EmbeddedSchemaAccess();
   Offset64Tests();
   UnionUnderlyingTypeTest();
+  StructsInHashTableTest();
   return 0;
 }
 }  // namespace
diff --git a/tests/union_vector/union_vector_generated.h b/tests/union_vector/union_vector_generated.h
index 1117efd..1fd25b0 100644
--- a/tests/union_vector/union_vector_generated.h
+++ b/tests/union_vector/union_vector_generated.h
@@ -384,6 +384,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const Rapunzel &obj) {
+  return H::combine(std::move(h), obj.hair_length());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) BookReader FLATBUFFERS_FINAL_CLASS {
  private:
@@ -417,6 +421,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const BookReader &obj) {
+  return H::combine(std::move(h), obj.books_read());
+}
 
 FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) FallingTub FLATBUFFERS_FINAL_CLASS {
  private:
@@ -450,6 +458,10 @@
     return !(lhs == rhs);
 }
 
+template <typename H>
+inline H AbslHashValue(H h, const FallingTub &obj) {
+  return H::combine(std::move(h), obj.weight());
+}
 
 struct AttackerT : public ::flatbuffers::NativeTable {
   typedef Attacker TableType;