Show a deprecation warning for old-style `__init__` and `__setstate__`

The warning is shown at module initialization time (on import, not
when the functions are called). It's only visible when compiled in
debug mode.
diff --git a/docs/upgrade.rst b/docs/upgrade.rst
index bcbc6b1..f993e20 100644
--- a/docs/upgrade.rst
+++ b/docs/upgrade.rst
@@ -37,6 +37,72 @@
     }
 
 
+New API for defining custom constructors and pickling functions
+---------------------------------------------------------------
+
+The old placement-new custom constructors have been deprecated. The new approach
+uses ``py::init()`` and factory functions to greatly improve type safety.
+
+Placement-new can be called accidentally with an incompatible type (without any
+compiler errors or warnings), or it can initialize the same object multiple times
+if not careful with the Python-side ``__init__`` calls. The new-style custom
+constructors prevent such mistakes. See :ref:`custom_constructors` for details.
+
+.. code-block:: cpp
+
+    // old -- deprecated (runtime warning shown only in debug mode)
+    py::class<Foo>(m, "Foo")
+        .def("__init__", [](Foo &self, ...) {
+            new (&self) Foo(...); // uses placement-new
+        });
+
+    // new
+    py::class<Foo>(m, "Foo")
+        .def(py::init([](...) { // Note: no `self` argument
+            return new Foo(...); // return by raw pointer
+            // or: return std::make_unique<Foo>(...); // return by holder
+            // or: return Foo(...); // return by value (move constructor)
+        }));
+
+Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred
+way to get and set object state. See :ref:`pickling` for details.
+
+.. code-block:: cpp
+
+    // old -- deprecated (runtime warning shown only in debug mode)
+    py::class<Foo>(m, "Foo")
+        ...
+        .def("__getstate__", [](const Foo &self) {
+            return py::make_tuple(self.value1(), self.value2(), ...);
+        })
+        .def("__setstate__", [](Foo &self, py::tuple t) {
+            new (&self) Foo(t[0].cast<std::string>(), ...);
+        });
+
+    // new
+    py::class<Foo>(m, "Foo")
+        ...
+        .def(py::pickle(
+            [](const Foo &self) { // __getstate__
+                return py::make_tuple(f.value1(), f.value2(), ...); // unchanged
+            },
+            [](py::tuple t) { // __setstate__, note: no `self` argument
+                return new Foo(t[0].cast<std::string>(), ...);
+                // or: return std::make_unique<Foo>(...); // return by holder
+                // or: return Foo(...); // return by value (move constructor)
+            }
+        ));
+
+For both the constructors and pickling, warnings are shown at module
+initialization time (on import, not when the functions are called).
+They're only visible when compiled in debug mode. Sample warning:
+
+.. code-block:: none
+
+    pybind11-bound class 'mymodule.Foo' is using an old-style placement-new '__init__'
+    which has been deprecated. See the upgrade guide in pybind11's docs.
+
+
 Stricter enforcement of hidden symbol visibility for pybind11 modules
 ---------------------------------------------------------------------
 
@@ -135,66 +201,6 @@
 third-party modules.
 
 
-New syntax for custom constructors
-----------------------------------
-
-The old placement-new custom constructors are still valid, but the new approach
-greatly improves type safety. Placement-new can be called accidentally with an
-incompatible type (without any compiler errors or warnings), or it can initialize
-the same object multiple times if not careful with the Python-side ``__init__``
-calls. The new-style ``py::init()`` custom constructors prevent such mistakes.
-See :ref:`custom_constructors` for details.
-
-.. code-block:: cpp
-
-    // old
-    py::class<Foo>(m, "Foo")
-        .def("__init__", [](Foo &self, ...) {
-            new (&self) Foo(...); // uses placement-new
-        });
-
-    // new
-    py::class<Foo>(m, "Foo")
-        .def(py::init([](...) { // Note: no `self` argument
-            return new Foo(...); // return by raw pointer
-            // or: return std::make_unique<Foo>(...); // return by holder
-            // or: return Foo(...); // return by value (move constructor)
-        }));
-
-
-New syntax for pickling support
--------------------------------
-
-Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred
-way to get and set object state. See :ref:`pickling` for details.
-
-.. code-block:: cpp
-
-    // old -- deprecated
-    py::class<Foo>(m, "Foo")
-        ...
-        .def("__getstate__", [](const Foo &self) {
-            return py::make_tuple(self.value1(), self.value2(), ...);
-        })
-        .def("__setstate__", [](Foo &self, py::tuple t) {
-            new (&self) Foo(t[0].cast<std::string>(), ...);
-        });
-
-    // new
-    py::class<Foo>(m, "Foo")
-        ...
-        .def(py::pickle(
-            [](const Foo &self) { // __getstate__
-                return py::make_tuple(f.value1(), f.value2(), ...); // unchanged
-            },
-            [](py::tuple t) { // __setstate__, note: no `self` argument
-                return new Foo(t[0].cast<std::string>(), ...);
-                // or: return std::make_unique<Foo>(...); // return by holder
-                // or: return Foo(...); // return by value (move constructor)
-            }
-        ));
-
-
 Deprecation of some ``py::object`` APIs
 ---------------------------------------
 
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 0e67c40..b6e5fa6 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -201,6 +201,20 @@
 
         rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
 
+#if !defined(NDEBUG) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING)
+        if (rec->is_constructor && !rec->is_new_style_constructor) {
+            const auto class_name = std::string(((PyTypeObject *) rec->scope.ptr())->tp_name);
+            const auto func_name = std::string(rec->name);
+            PyErr_WarnEx(
+                PyExc_FutureWarning,
+                ("pybind11-bound class '" + class_name + "' is using an old-style "
+                 "placement-new '" + func_name + "' which has been deprecated. See "
+                 "the upgrade guide in pybind11's docs. This message is only visible "
+                 "when compiled in debug mode.").c_str(), 0
+            );
+        }
+#endif
+
         /* Generate a proper function signature */
         std::string signature;
         size_t type_depth = 0, char_index = 0, type_index = 0, arg_index = 0;