diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 9d62420f240338d5926f7f3963234643e514393f..7b171d71b4c33d0f24cbc239039f548a8613759e 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -39,7 +39,24 @@ if(NOT NUMPY_WITH_BROKEN_UFUNC_SUPPORT)
 endif()
 add_lib_unit_test(std_vector)
 add_lib_unit_test(user_struct)
-add_lib_unit_test(bind_optional)
+
+function(config_bind_optional tagname opttype)
+  set(MODNAME bind_optional_${tagname})
+  set(OPTIONAL ${opttype})
+  configure_file(bind_optional.cpp.in ${MODNAME}.cpp)
+
+  set(py_file test_optional_${tagname}.py)
+  configure_file(python/test_optional.py.in ${CMAKE_CURRENT_SOURCE_DIR}/python/${py_file})
+  add_lib_unit_test(${MODNAME})
+  message(STATUS "Adding unit test py-optional-${tagname} with file ${py_file} and module ${MODNAME}")
+  add_python_unit_test("py-optional-${tagname}" "unittest/python/${py_file}"
+                       "unittest")
+endfunction()
+
+config_bind_optional(boost "boost::optional")
+if(CMAKE_CXX_STANDARD GREATER 14 AND CMAKE_CXX_STANDARD LESS 98)
+  config_bind_optional(std "std::optional")
+endif()
 
 add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
 
@@ -98,6 +115,3 @@ set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
 add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
                      "python;unittest")
 set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
-
-add_python_unit_test("py-optional" "unittest/python/test_optional.py"
-                     "unittest")
diff --git a/unittest/bind_optional.cpp b/unittest/bind_optional.cpp.in
similarity index 73%
rename from unittest/bind_optional.cpp
rename to unittest/bind_optional.cpp.in
index 6d85795c4306e2f435250f6deffcceb14aba9f33..f6827872af0d9433c634d0d2c31c344b1ee38d8f 100644
--- a/unittest/bind_optional.cpp
+++ b/unittest/bind_optional.cpp.in
@@ -1,16 +1,20 @@
 #include "eigenpy/eigenpy.hpp"
 #include "eigenpy/optional.hpp"
+#ifdef EIGENPY_WITH_CXX17_SUPPORT
+#include <optional>
+#endif
 
-#define OPTIONAL boost::optional
-#define OPT_NONE boost::none
+#cmakedefine OPTIONAL @OPTIONAL@
 
-using opt_dbl = OPTIONAL<double>;
+typedef eigenpy::detail::nullopt_helper<OPTIONAL> none_helper;
+static auto OPT_NONE = none_helper::value();
+typedef OPTIONAL<double> opt_dbl;
 
 struct mystruct {
   OPTIONAL<int> a;
   opt_dbl b;
   OPTIONAL<std::string> msg{"i am struct"};
-  mystruct() : a(OPT_NONE), b(boost::none) {}
+  mystruct() : a(OPT_NONE), b(OPT_NONE) {}
   mystruct(int a, const opt_dbl &b = OPT_NONE) : a(a), b(b) {}
 };
 
@@ -36,13 +40,13 @@ OPTIONAL<Eigen::MatrixXd> random_mat_if_true(bool flag) {
     return OPT_NONE;
 }
 
-BOOST_PYTHON_MODULE(bind_optional) {
+BOOST_PYTHON_MODULE(@MODNAME@) {
   using namespace eigenpy;
-  OptionalConverter<int>::registration();
-  OptionalConverter<double>::registration();
-  OptionalConverter<std::string>::registration();
-  OptionalConverter<mystruct>::registration();
-  OptionalConverter<Eigen::MatrixXd>::registration();
+  OptionalConverter<int, OPTIONAL>::registration();
+  OptionalConverter<double, OPTIONAL>::registration();
+  OptionalConverter<std::string, OPTIONAL>::registration();
+  OptionalConverter<mystruct, OPTIONAL>::registration();
+  OptionalConverter<Eigen::MatrixXd, OPTIONAL>::registration();
   enableEigenPy();
 
   bp::class_<mystruct>("mystruct", bp::no_init)
diff --git a/unittest/python/test_optional.py b/unittest/python/test_optional.py.in
similarity index 88%
rename from unittest/python/test_optional.py
rename to unittest/python/test_optional.py.in
index d6a29738c2b4c77edfadba79b22d3a6c334fc8e3..bd5e085ec076bf2a5ce3ed8645f3b3624b95b494 100644
--- a/unittest/python/test_optional.py
+++ b/unittest/python/test_optional.py.in
@@ -1,4 +1,6 @@
-import bind_optional
+import importlib
+
+bind_optional = importlib.import_module("@MODNAME@")
 
 
 def test_none_if_zero():
@@ -58,8 +60,8 @@ def test_random_mat():
     assert M.shape == (4, 4)
 
 
-if __name__ == "__main__":
-    import pytest
-    import sys
-
-    sys.exit(pytest.main(sys.argv))
+test_none_if_zero()
+test_struct_ctors()
+test_struct_setters()
+test_factory()
+test_random_mat()
diff --git a/unittest/python/test_optional_boost.py b/unittest/python/test_optional_boost.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc818739d90e89be9d41f8f26c0f1f560ef5f4b7
--- /dev/null
+++ b/unittest/python/test_optional_boost.py
@@ -0,0 +1,67 @@
+import importlib
+
+bind_optional = importlib.import_module("bind_optional_boost")
+
+
+def test_none_if_zero():
+    x = bind_optional.none_if_zero(0)
+    y = bind_optional.none_if_zero(-1)
+    assert x is None
+    assert y == -1
+
+
+def test_struct_ctors():
+    # test struct ctors
+
+    struct = bind_optional.mystruct()
+    assert struct.a is None
+    assert struct.b is None
+    assert struct.msg == "i am struct"
+
+    ## no 2nd arg automatic overload using bp::optional
+    struct = bind_optional.mystruct(2)
+    assert struct.a == 2
+    assert struct.b is None
+
+    struct = bind_optional.mystruct(13, -1.0)
+    assert struct.a == 13
+    assert struct.b == -1.0
+
+
+def test_struct_setters():
+    struct = bind_optional.mystruct()
+    struct.a = 1
+    assert struct.a == 1
+
+    struct.b = -3.14
+    assert struct.b == -3.14
+
+    # set to None
+    struct.a = None
+    struct.b = None
+    struct.msg = None
+    assert struct.a is None
+    assert struct.b is None
+    assert struct.msg is None
+
+
+def test_factory():
+    struct = bind_optional.create_if_true(False, None)
+    assert struct is None
+    struct = bind_optional.create_if_true(True, None)
+    assert struct.a == 0
+    assert struct.b is None
+
+
+def test_random_mat():
+    M = bind_optional.random_mat_if_true(False)
+    assert M is None
+    M = bind_optional.random_mat_if_true(True)
+    assert M.shape == (4, 4)
+
+
+test_none_if_zero()
+test_struct_ctors()
+test_struct_setters()
+test_factory()
+test_random_mat()
diff --git a/unittest/python/test_optional_std.py b/unittest/python/test_optional_std.py
new file mode 100644
index 0000000000000000000000000000000000000000..69949a44f46b3f2ea010af24a3478d0a67fb8e5f
--- /dev/null
+++ b/unittest/python/test_optional_std.py
@@ -0,0 +1,67 @@
+import importlib
+
+bind_optional = importlib.import_module("bind_optional_std")
+
+
+def test_none_if_zero():
+    x = bind_optional.none_if_zero(0)
+    y = bind_optional.none_if_zero(-1)
+    assert x is None
+    assert y == -1
+
+
+def test_struct_ctors():
+    # test struct ctors
+
+    struct = bind_optional.mystruct()
+    assert struct.a is None
+    assert struct.b is None
+    assert struct.msg == "i am struct"
+
+    ## no 2nd arg automatic overload using bp::optional
+    struct = bind_optional.mystruct(2)
+    assert struct.a == 2
+    assert struct.b is None
+
+    struct = bind_optional.mystruct(13, -1.0)
+    assert struct.a == 13
+    assert struct.b == -1.0
+
+
+def test_struct_setters():
+    struct = bind_optional.mystruct()
+    struct.a = 1
+    assert struct.a == 1
+
+    struct.b = -3.14
+    assert struct.b == -3.14
+
+    # set to None
+    struct.a = None
+    struct.b = None
+    struct.msg = None
+    assert struct.a is None
+    assert struct.b is None
+    assert struct.msg is None
+
+
+def test_factory():
+    struct = bind_optional.create_if_true(False, None)
+    assert struct is None
+    struct = bind_optional.create_if_true(True, None)
+    assert struct.a == 0
+    assert struct.b is None
+
+
+def test_random_mat():
+    M = bind_optional.random_mat_if_true(False)
+    assert M is None
+    M = bind_optional.random_mat_if_true(True)
+    assert M.shape == (4, 4)
+
+
+test_none_if_zero()
+test_struct_ctors()
+test_struct_setters()
+test_factory()
+test_random_mat()