From 9a0863b4f49fdee6f7ab9970cc994256ec63188a Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Fri, 5 Jan 2018 16:11:42 -0500 Subject: [PATCH] Add `type_pack` meta-programming utilities. --- bindings/pydrake/BUILD.bazel | 14 ++ bindings/pydrake/test/type_pack_test.cc | 121 ++++++++++++++ bindings/pydrake/type_pack.h | 199 ++++++++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 bindings/pydrake/test/type_pack_test.cc create mode 100644 bindings/pydrake/type_pack.h diff --git a/bindings/pydrake/BUILD.bazel b/bindings/pydrake/BUILD.bazel index 84bf67cfd7e2..85a6006674fb 100644 --- a/bindings/pydrake/BUILD.bazel +++ b/bindings/pydrake/BUILD.bazel @@ -4,6 +4,7 @@ load("@drake//tools/install:install.bzl", "install") load("//tools/lint:lint.bzl", "add_lint_tests") load( "//tools:drake.bzl", + "drake_cc_googletest", "drake_cc_library", ) load( @@ -29,6 +30,11 @@ package(default_visibility = adjust_labels_for_drake_hoist([ # N.B. Any common headers should be shared via a HEADER ONLY library. +drake_cc_library( + name = "type_pack", + hdrs = ["type_pack.h"], +) + # Target Naming Conventions: # `*_py` # A Python library (can be pure Python or pybind) @@ -160,6 +166,14 @@ drake_py_library( deps = PYBIND_LIBRARIES + PY_LIBRARIES, ) +drake_cc_googletest( + name = "type_pack_test", + deps = [ + ":type_pack", + "//common:nice_type_name", + ], +) + # Test ODR (One Definition Rule). drake_pybind_library( name = "odr_test_module_py", diff --git a/bindings/pydrake/test/type_pack_test.cc b/bindings/pydrake/test/type_pack_test.cc new file mode 100644 index 000000000000..7a850a5b308a --- /dev/null +++ b/bindings/pydrake/test/type_pack_test.cc @@ -0,0 +1,121 @@ +#include "drake/bindings/pydrake/type_pack.h" + +#include +#include + +#include + +#include "drake/common/nice_type_name.h" + +using std::string; +using std::vector; + +namespace drake { +namespace { + +template +struct SimpleTemplate {}; + +using Pack = type_pack; + +// Mostly, this just checks for compilation failures. +GTEST_TEST(TypeUtilTest, TypeAt) { + using T_0 = type_at<0, int, double, char, void>::type; + EXPECT_TRUE((std::is_same::value)); + using T_1 = type_at<1, int, double, char, void>::type; + EXPECT_TRUE((std::is_same::value)); + using T_2 = type_at<2, int, double, char, void>::type; + EXPECT_TRUE((std::is_same::value)); + using T_3 = type_at<3, int, double, char, void>::type; + EXPECT_TRUE((std::is_same::value)); + + EXPECT_TRUE((std::is_same, int>::value)); + EXPECT_TRUE((std::is_same, double>::value)); + EXPECT_TRUE((std::is_same, char>::value)); + EXPECT_TRUE((std::is_same, void>::value)); +} + +GTEST_TEST(TypeUtilTest, TypeTags) { + // Ensure that we can default-construct tags for types that are not + // default-constructible. + auto tag_check = type_tag{}; + EXPECT_TRUE((std::is_same< + decltype(tag_check), type_tag>::value)); + auto pack_check_empty = type_pack<>{}; + EXPECT_TRUE((std::is_same< + decltype(pack_check_empty), type_pack<>>::value)); + auto pack_check = type_pack{}; + EXPECT_TRUE((std::is_same< + decltype(pack_check), type_pack>::value)); +} + +GTEST_TEST(TypeUtilTest, Bind) { + using T_0 = Pack::bind; + EXPECT_TRUE((std::is_same< + T_0, SimpleTemplate>::value)); + Pack pack; + using T_1 = decltype(type_bind(pack)); + EXPECT_TRUE((std::is_same< + T_1, SimpleTemplate>::value)); +} + +GTEST_TEST(TypeUtilTest, Extract) { + using T = SimpleTemplate; + using TPack = type_pack_extract; + EXPECT_TRUE((std::is_same::value)); +} + +GTEST_TEST(TypeUtilTest, Visit) { + using PackTags = type_pack< + type_tag, type_tag, type_tag, type_tag>; + vector names; + const vector names_expected = {"int", "double", "char", "void"}; + + auto visitor = [&names](auto tag) { + using T = typename decltype(tag)::type; + names.push_back(NiceTypeName::Get()); + }; + names.clear(); + type_visit(visitor, PackTags{}); + EXPECT_EQ(names, names_expected); + + names.clear(); + type_visit>(visitor, Pack{}); + EXPECT_EQ(names, names_expected); + + // N.B. `Check` will operate types contained within the pack as dictated by + // the `VisitWith` parameter in `type_visit`. + // As an example, see below that the two visit calls will be visiting the + // same types (with the same results), but `Check` will operate on + // (a) the direct types with `type_visit_with_tag<>` and (b) the tag types + // with `type_visit_with_default` (the default parameter). + const vector names_expected_sub = {"int", "char", "void"}; + + names.clear(); + type_visit, type_check_different_from::type>( + visitor, Pack{}); + EXPECT_EQ(names, names_expected_sub); + + names.clear(); + type_visit(visitor, PackTags{}, + template_tag>::type>{}); + EXPECT_EQ(names, names_expected_sub); +}; + +GTEST_TEST(TypeUtilTest, Transform) { + auto seq = sequence_transform( + constant_add{}, std::make_integer_sequence{}); + EXPECT_TRUE((std::is_same< + decltype(seq), std::integer_sequence>::value)); +} + +GTEST_TEST(TypeUtilTest, Hash) { + using T = int; + using U = int; + using V = double; + EXPECT_EQ(type_hash(), type_hash()); + EXPECT_NE(type_hash(), type_hash()); +} + +} // namespace +} // namespace drake diff --git a/bindings/pydrake/type_pack.h b/bindings/pydrake/type_pack.h new file mode 100644 index 000000000000..3b2cad0340a0 --- /dev/null +++ b/bindings/pydrake/type_pack.h @@ -0,0 +1,199 @@ +#pragma once + +/// @file +/// Basic meta-programming utilities for types, focused on template parameter +/// packs. + +#include +#include +#include +#include +#include + +namespace drake { + +template +struct type_pack; + +namespace detail { + +// Provides type at given index. +template +struct type_at_impl { + using type = typename type_at_impl::type; +}; + +// Base case. +template +struct type_at_impl { + using type = T; +}; + +// Visits a type given a VisitWith mechanism, templated to permit +// conditional execution. +template +struct type_visit_impl { + template + struct runner { + inline static void run(Visitor&& visitor) { + VisitWith::template run(std::forward(visitor)); + } + }; + template + struct runner { + inline static void run(Visitor&&) {} + }; +}; + +// Catches non-template types explicitly. +template +struct type_pack_extract_impl { + // Defer to show that this is a bad instantiation. + static_assert(!std::is_same::value, "Wrong template"); +}; + +template