From 23f6418707102b80a1d26127e2f8530f853b5510 Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Thu, 20 Dec 2018 16:49:49 +0100 Subject: [PATCH 1/2] type_name : add support for MSVC (compile time) and GCC (run time) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Details: ``` include/boost/hana/experimental/ ├── detail │ ├── type_name_compiler_capabilities.hpp │ ├── type_name_pretty_function.hpp │ └── type_name_stringliteral.hpp ├── type_name.hpp test/experimental/ ├── type_name.cpp ├── type_name_stringliteral_test.cpp ``` * include/boost/hana/experimental/detail The design choices for the files in this folder is that they should run without any dependency (not even a dependency on Boost.Hana). The main API for them is inside type_name_pretty_function.hpp. These files do not use the `BOOST_HANA_NAMESPACE_BEGIN` macro, in order not to depend on Boost.Hana `test/experimental/type_name_stringliteral_test.cpp` is a standalone test for these files. The intent is to be able to provide them as testing tools for compiler implementers, notably GCC which does not support constexpr string literals. The namespace in these files was renamed to `type_name_details::` * type_name_pretty_function.hpp * Provides experimental::type_name_details::type_name_impl_stringliteral() * Does this by defining a different prefix/suffix per compiler (_HANA_TN_PRETTY_FUNCTION_TYPE_PREFIX, _HANA_TN_PRETTY_FUNCTION_TYPE_SUFFIX) * Also provides a compiler agnostic _HANA_TN__PRETTY_FUNCTION__ * type_name_compiler_capabilities.hpp : Defines _HANA_TN_CAN_CONSTEXPR for MSVC and Clang Implements _HANA_SIZEOF_OR_STRLEN as sizeof or strlen * type_name_stringliteral.hpp : Renames `cstring` to `stringliteral` and adds some limited capabilities (_HANA_TN_MAKE_STRINGLITERAL, stringliteral_equal, stringliteral_equal_sz, stringliteral_to_string) * include/boost/hana/experimental/type_name.hpp includes hana/experimental/detail/type_name_pretty_function.hpp and uses `type_name_impl_stringliteral` from type_name_pretty_function.hpp * test/experimental/type_name.cpp contains the same tests as before, but it will be run under MSVC, Clang, and GCC (runtime only). There are two adaptations for MSVC, without a regex because it makes it easier to understand the kind of output MSVC produces. * test/type_name_stringliteral_test.cpp contains the same tests, except that they are independent from Boost.Hana It also adds another test concerning the fact that __PRETTY_FUNCTION__ is west const on GCC/Clang/MSVC. However I think this test is probably too much. * Those tests were re-enabled under GCC and MSVC --- .../type_name_compiler_capabilities.hpp | 29 +++++++ .../detail/type_name_pretty_function.hpp | 59 +++++++++++++ .../detail/type_name_stringliteral.hpp | 72 ++++++++++++++++ include/boost/hana/experimental/type_name.hpp | 55 +++++------- test/CMakeLists.txt | 20 +++-- test/experimental/type_name.cpp | 26 ++++++ .../type_name_stringliteral_test.cpp | 86 +++++++++++++++++++ 7 files changed, 306 insertions(+), 41 deletions(-) create mode 100644 include/boost/hana/experimental/detail/type_name_compiler_capabilities.hpp create mode 100644 include/boost/hana/experimental/detail/type_name_pretty_function.hpp create mode 100644 include/boost/hana/experimental/detail/type_name_stringliteral.hpp create mode 100644 test/experimental/type_name_stringliteral_test.cpp 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..ea79cc981c --- /dev/null +++ b/test/experimental/type_name_stringliteral_test.cpp @@ -0,0 +1,86 @@ +#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; +} From 37ab0dd4a45f2fdec9657a9acae0efff80e68e40 Mon Sep 17 00:00:00 2001 From: pthom Date: Fri, 21 Dec 2018 16:48:06 +0100 Subject: [PATCH 2/2] ping travis Since the last build failed due to network issues inside travis --- test/experimental/type_name_stringliteral_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/experimental/type_name_stringliteral_test.cpp b/test/experimental/type_name_stringliteral_test.cpp index ea79cc981c..4b79e60d59 100644 --- a/test/experimental/type_name_stringliteral_test.cpp +++ b/test/experimental/type_name_stringliteral_test.cpp @@ -7,7 +7,6 @@ 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( \