diff --git a/include/boost/hana/experimental/detail/type_name_compiler_capabilities.hpp b/include/boost/hana/experimental/detail/type_name_compiler_capabilities.hpp new file mode 100644 index 0000000000..0eed26b0d6 --- /dev/null +++ b/include/boost/hana/experimental/detail/type_name_compiler_capabilities.hpp @@ -0,0 +1,29 @@ +/* +@file +Defines _HANA_TN_CAN_CONSTEXPR and related macros +(_HANA_TN_CONSTEXPR_IF_POSSIBLE, _HANA_SIZEOF_OR_STRLEN) + +@copyright Louis Dionne 2013-2017 +Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_COMPILER_CAPABILITIES_HPP +#define BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_COMPILER_CAPABILITIES_HPP + +// only clang and MSVC support constexpr __PRETTY_FUNCTION__, gcc does not +#if defined(__clang__) || defined(_MSC_VER) + #define _HANA_TN_CAN_CONSTEXPR +#endif + +// in constexpr mode, strlen is equivalent to sizeof() - 1 +#ifdef _HANA_TN_CAN_CONSTEXPR + #define _HANA_TN_CONSTEXPR_IF_POSSIBLE constexpr + #define _HANA_SIZEOF_OR_STRLEN(var) sizeof(var) - 1 +#else + #include // this include is not needed in constexpr mode, save compilation time + #define _HANA_TN_CONSTEXPR_IF_POSSIBLE + #define _HANA_SIZEOF_OR_STRLEN(var) strlen(var) +#endif + +#endif // !BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_COMPILER_CAPABILITIES_HPP diff --git a/include/boost/hana/experimental/detail/type_name_pretty_function.hpp b/include/boost/hana/experimental/detail/type_name_pretty_function.hpp new file mode 100644 index 0000000000..2db09a9c26 --- /dev/null +++ b/include/boost/hana/experimental/detail/type_name_pretty_function.hpp @@ -0,0 +1,59 @@ +/* +@file +Defines hana::experimental::type_name_details::type_name_impl_stringliteral(). +Also defines _HANA_TN__PRETTY_FUNCTION__ and related defines. + +@copyright Louis Dionne 2013-2017 +Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_PRETTY_FUNCTION_HPP +#define BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_PRETTY_FUNCTION_HPP + +#include +#include +#include + +#ifdef _MSC_VER +#define _HANA_TN__PRETTY_FUNCTION__ __FUNCSIG__ +#else +#define _HANA_TN__PRETTY_FUNCTION__ __PRETTY_FUNCTION__ +#endif + + +#if defined(__clang__) + #define _HANA_TN_PRETTY_FUNCTION_TYPE_PREFIX "boost::hana::experimental::type_name_details::stringliteral boost::hana::experimental::type_name_details::type_name_impl_stringliteral() [T = " + #define _HANA_TN_PRETTY_FUNCTION_TYPE_SUFFIX "]" +#elif defined(_MSC_VER) + #define _HANA_TN_PRETTY_FUNCTION_TYPE_PREFIX "struct boost::hana::experimental::type_name_details::stringliteral __cdecl boost::hana::experimental::type_name_details::type_name_impl_stringliteral<" + #define _HANA_TN_PRETTY_FUNCTION_TYPE_SUFFIX ">(void)" +#elif defined(__GNUC__) || defined(__GNUG__) + #define _HANA_TN_PRETTY_FUNCTION_TYPE_PREFIX "constexpr boost::hana::experimental::type_name_details::stringliteral boost::hana::experimental::type_name_details::type_name_impl_stringliteral() [with T = " + #define _HANA_TN_PRETTY_FUNCTION_TYPE_SUFFIX "]" +#else + #error "No support for this compiler." +#endif + + +namespace boost { +namespace hana { +namespace experimental { + + namespace type_name_details { + + template + constexpr stringliteral type_name_impl_stringliteral() { + _HANA_TN_CONSTEXPR_IF_POSSIBLE char const* pretty_function = _HANA_TN__PRETTY_FUNCTION__; + _HANA_TN_CONSTEXPR_IF_POSSIBLE std::size_t total_size = _HANA_SIZEOF_OR_STRLEN(_HANA_TN__PRETTY_FUNCTION__); + _HANA_TN_CONSTEXPR_IF_POSSIBLE std::size_t prefix_size = _HANA_SIZEOF_OR_STRLEN(_HANA_TN_PRETTY_FUNCTION_TYPE_PREFIX); + _HANA_TN_CONSTEXPR_IF_POSSIBLE std::size_t suffix_size = _HANA_SIZEOF_OR_STRLEN(_HANA_TN_PRETTY_FUNCTION_TYPE_SUFFIX); + return {pretty_function + prefix_size, total_size - prefix_size - suffix_size}; + } + } // end namespace type_name_details + +} // namespace experimental2 +} // namespace hana +} // namespace boost + +#endif // !BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_PRETTY_FUNCTION_HPP diff --git a/include/boost/hana/experimental/detail/type_name_stringliteral.hpp b/include/boost/hana/experimental/detail/type_name_stringliteral.hpp new file mode 100644 index 0000000000..9724172b35 --- /dev/null +++ b/include/boost/hana/experimental/detail/type_name_stringliteral.hpp @@ -0,0 +1,72 @@ +/* +@file +Defines + hana::experimental::type_name_details::stringliteral + _HANA_TN_MAKE_STRINGLITERAL + +@copyright Louis Dionne 2013-2017 +Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_STRINGLITERAL_HPP +#define BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_STRINGLITERAL_HPP + +#include + +#include +#include +#include + + +namespace boost { +namespace hana { +namespace experimental { +namespace type_name_details { + + struct stringliteral { + char const* ptr; + std::size_t length; + }; + + +#define _HANA_TN_MAKE_STRINGLITERAL(str_literal) stringliteral { str_literal, _HANA_SIZEOF_OR_STRLEN(str_literal); + + + inline constexpr std::size_t constexpr_strlen(char const * s) { + std::size_t r = 0; + while(*s++ != '\0') + r++; + return r; + } + + inline constexpr bool stringliteral_equal(stringliteral const & cs1, stringliteral const & cs2) { + if (cs1.length != cs2.length) + return false; + + std::size_t idx = 0; + do { + if (cs1.ptr[idx] != cs2.ptr[idx]) + return false; + idx++; + } while (idx < cs1.length); + return true; + } + + inline constexpr bool stringliteral_equal_sz(stringliteral const & cs1, char const * literal) { + return stringliteral_equal( + cs1, + { literal, constexpr_strlen(literal) } + ); + } + + inline std::string stringliteral_to_string(stringliteral const & cs) { + return std::string(cs.ptr, cs.length); + } + +} // namespace type_name_details +} // namespace experimental +} // namespace hana +} // namespace boost + +#endif // !BOOST_HANA_EXPERIMENTAL_DETAIL_TYPE_NAME_STRINGLITERAL_HPP diff --git a/include/boost/hana/experimental/type_name.hpp b/include/boost/hana/experimental/type_name.hpp index f3db176178..dae42ef360 100644 --- a/include/boost/hana/experimental/type_name.hpp +++ b/include/boost/hana/experimental/type_name.hpp @@ -12,52 +12,39 @@ Distributed under the Boost Software License, Version 1.0. #include #include +#include -#include #include -BOOST_HANA_NAMESPACE_BEGIN namespace experimental { - namespace detail { - struct cstring { - char const* ptr; - std::size_t length; - }; - - // Note: We substract the null terminator from the string sizes below. - template - constexpr cstring type_name_impl2() { - - #if defined(__clang__) - constexpr char const* pretty_function = __PRETTY_FUNCTION__; - constexpr std::size_t total_size = sizeof(__PRETTY_FUNCTION__) - 1; - constexpr std::size_t prefix_size = sizeof("boost::hana::experimental::detail::cstring boost::hana::experimental::detail::type_name_impl2() [T = ") - 1; - constexpr std::size_t suffix_size = sizeof("]") - 1; - #else - #error "No support for this compiler." - #endif - - return {pretty_function + prefix_size, total_size - prefix_size - suffix_size}; - } - +BOOST_HANA_NAMESPACE_BEGIN namespace experimental { + namespace type_name_details { template - auto type_name_impl1(std::index_sequence) { - constexpr auto name = detail::type_name_impl2(); - return hana::string<*(name.ptr + i)...>{}; + auto type_name_impl(std::index_sequence) { + constexpr auto name = type_name_details::type_name_impl_stringliteral(); + return boost::hana::string<*(name.ptr + i)...>{}; } - } // end namespace detail + } // end namespace type_name_details //! @ingroup group-experimental //! Returns a `hana::string` representing the name of the given type, at - //! compile-time. + //! compile-time. This works on Clang and MSVC 2017. + //! + //! It also works on GCC, but only at run time and it will return + //! a `std::string`. //! - //! This only works on Clang (and apparently MSVC, but Hana does not work - //! there as of writing this). Original idea taken from - //! https://github.com/Manu343726/ctti. + //! Other compilers are not supported as of writing this. + //! Original idea taken from https://github.com/Manu343726/ctti. template auto type_name() { - constexpr auto name = detail::type_name_impl2(); - return detail::type_name_impl1(std::make_index_sequence{}); + #ifdef _HANA_TN_CAN_CONSTEXPR + constexpr auto name = type_name_details::type_name_impl_stringliteral(); + return type_name_details::type_name_impl(std::make_index_sequence{}); + #else + return type_name_details::stringliteral_to_string( + type_name_details::type_name_impl_stringliteral() + ); + #endif } } BOOST_HANA_NAMESPACE_END diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d3b9df8d14..5c87912521 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,15 +28,21 @@ if (NOT Boost_FOUND) list(APPEND EXCLUDED_PUBLIC_HEADERS ${PUBLIC_HEADERS_REQUIRING_BOOST}) endif() -# The experimental::type_name test is only supported on Clang >= 3.6 and -# AppleClang >= 7.0 -if (NOT ((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" AND - NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.6) - OR (${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang" AND - NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7))) +# The experimental::type_name test is not supported +# on Clang < 3.6 and AppleClang < 7.0 +if ( + ( (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + AND (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.6) ) + OR + ( (${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") + AND (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7 )) + ) list(APPEND EXCLUDED_PUBLIC_HEADERS "boost/hana/experimental/type_name.hpp") - list(APPEND EXCLUDED_UNIT_TESTS "experimental/type_name.cpp") + list(APPEND EXCLUDED_UNIT_TESTS + "experimental/type_name.cpp") + list(APPEND EXCLUDED_UNIT_TESTS + "experimental/type_name_stringliteral_test.cpp") endif() # On Windows, Clang-cl emulates a MSVC bug that causes EBO not to be applied diff --git a/test/experimental/type_name.cpp b/test/experimental/type_name.cpp index 6144dc7195..65b94cb079 100644 --- a/test/experimental/type_name.cpp +++ b/test/experimental/type_name.cpp @@ -19,7 +19,11 @@ struct Template { }; template void check_matches(std::string const& re) { +#ifdef _HANA_TN_CAN_CONSTEXPR std::string name = hana::to(hana::experimental::type_name()); +#else + std::string name = hana::experimental::type_name(); +#endif std::regex regex{re}; if (!std::regex_match(name, regex)) { std::cerr << "type name '" << name << "' does not match regex '" << re << "'" << std::endl; @@ -27,7 +31,22 @@ void check_matches(std::string const& re) { } } +template +void check_exact(std::string const& expected) { +#ifdef _HANA_TN_CAN_CONSTEXPR + std::string name = hana::to(hana::experimental::type_name()); +#else + std::string name = hana::experimental::type_name(); +#endif + if (name != expected) { + std::cerr << "type name '" << name << "' does not match expected '" << expected << "'" << std::endl; + std::abort(); + } +} + + int main() { +#ifdef _HANA_TN_CAN_CONSTEXPR // Make sure this can be obtained at compile-time BOOST_HANA_CONSTANT_CHECK(hana::equal( hana::experimental::type_name(), @@ -38,6 +57,7 @@ int main() { hana::experimental::type_name(), BOOST_HANA_STRING("int") )); +#endif // Make sure we get something reasonable check_matches("int const|const int"); @@ -45,6 +65,12 @@ int main() { check_matches(R"(const\s+int\s*&|int\s+const\s*&)"); check_matches(R"(int\s*\(\s*&\s*\)\s*\[\s*\])"); check_matches(R"(int\s*\(\s*&\s*\)\s*\[\s*10\s*\])"); +#ifndef _MSC_VER check_matches>(R"(Template<\s*void\s*,\s*(char const|const char)\s*\*\s*>)"); check_matches(R"(void\s*\(\s*\*\s*\)\s*\(\s*int\s*\))"); +#else + // MSVC adds superfluous "struct" and/or "__cdecl" keywords + check_exact>("struct Template"); + check_exact("void(__cdecl *)(int)"); +#endif } diff --git a/test/experimental/type_name_stringliteral_test.cpp b/test/experimental/type_name_stringliteral_test.cpp new file mode 100644 index 0000000000..4b79e60d59 --- /dev/null +++ b/test/experimental/type_name_stringliteral_test.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include +#include +#include + +namespace type_name_details = boost::hana::experimental::type_name_details; + +#ifdef _HANA_TN_CAN_CONSTEXPR + #define RUN_ONE_TYPE_TEST_COMPILE_TIME(type_definition, type_string_literal) \ + static_assert( \ + type_name_details::stringliteral_equal_sz( \ + type_name_details::type_name_impl_stringliteral(), \ + type_string_literal), \ + "RUN_ONE_TYPE_TEST_COMPILE_TIME error"); +#else + #define RUN_ONE_TYPE_TEST_COMPILE_TIME(type_definition, type_string_literal) +#endif + + +template +struct Template { +}; + + +template +void check_matches(std::string const& re) { + type_name_details::stringliteral name_cs = type_name_details::type_name_impl_stringliteral(); + std::string name = type_name_details::stringliteral_to_string(name_cs); + std::regex regex{re}; + if (!std::regex_match(name, regex)) { + std::cerr << "type name '" << name << "' does not match regex '" << re << "'" << std::endl; + std::abort(); + } +} + +template +void check_exact(std::string const& expected) { + type_name_details::stringliteral name_cs = type_name_details::type_name_impl_stringliteral(); + std::string name = type_name_details::stringliteral_to_string(name_cs); + if (name != expected) { + std::cerr << "type name '" << name << "' does not match expected '" << expected << "'" << std::endl; + std::abort(); + } +} + + +void compile_time_tests() { + RUN_ONE_TYPE_TEST_COMPILE_TIME(void, "void"); + RUN_ONE_TYPE_TEST_COMPILE_TIME(char, "char"); + + // __PRETTY_FUNCTION__ seems to favor west-const + // (however its behavior is somewhat inconsistent on complex types) + // + // On the contrary, typeid().name() is consistently east const accross all compilers + // Does this need to be tested ? + RUN_ONE_TYPE_TEST_COMPILE_TIME(const char, "const char"); + RUN_ONE_TYPE_TEST_COMPILE_TIME(char const, "const char"); +} + + +void runtime_regex_tests() { + // Make sure we get something reasonable + check_matches("int const|const int"); + check_matches(R"(int\s*&)"); + check_matches(R"(const\s+int\s*&|int\s+const\s*&)"); + check_matches(R"(int\s*\(\s*&\s*\)\s*\[\s*\])"); + check_matches(R"(int\s*\(\s*&\s*\)\s*\[\s*10\s*\])"); +#ifndef _MSC_VER + check_matches>(R"(Template<\s*void\s*,\s*(char const|const char)\s*\*\s*>)"); + check_matches(R"(void\s*\(\s*\*\s*\)\s*\(\s*int\s*\))"); +#else + // MSVC adds superfluous "struct" and/or "__cdecl" keywords + check_exact>("struct Template"); + check_exact("void(__cdecl *)(int)"); +#endif +} + + +int main() { + compile_time_tests(); + runtime_regex_tests(); + return 0; +}