feat: py::type::of<T>() and py::type::of(h) (#2364)

* feat: type<T>()

* refactor: using py::type as class

* refactor: py::object as base

* wip: tigher api

* refactor: fix conversion and limit API further

* docs: some added notes from @EricCousineau-TRI

* refactor: use py::type::of
diff --git a/tests/test_class.py b/tests/test_class.py
index 4214fe7..be21f37 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -26,6 +26,40 @@
     assert cstats.alive() == 0
 
 
+def test_type():
+    assert m.check_type(1) == m.DerivedClass1
+    with pytest.raises(RuntimeError) as execinfo:
+        m.check_type(0)
+
+    assert 'pybind11::detail::get_type_info: unable to find type info' in str(execinfo.value)
+    assert 'Invalid' in str(execinfo.value)
+
+    # Currently not supported
+    # See https://github.com/pybind/pybind11/issues/2486
+    # assert m.check_type(2) == int
+
+
+def test_type_of_py():
+    assert m.get_type_of(1) == int
+    assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1
+    assert m.get_type_of(int) == type
+
+
+def test_type_of_py_nodelete():
+    # If the above test deleted the class, this will segfault
+    assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1
+
+
+def test_as_type_py():
+    assert m.as_type(int) == int
+
+    with pytest.raises(RuntimeError):
+        assert m.as_type(1) == int
+
+    with pytest.raises(RuntimeError):
+        assert m.as_type(m.DerivedClass1()) == m.DerivedClass1
+
+
 def test_docstrings(doc):
     assert doc(UserType) == "A `py::class_` type for testing"
     assert UserType.__name__ == "UserType"