Advertisement

Compile time type name formatting

Started by August 06, 2018 12:48 PM
2 comments, last by irreversible 6 years, 1 month ago

I'm looking into CTTI, which provides some nifty compile-time type info resolution. However, it does so in a marginally inconvenient way (and by marginally I really do mean marginally) as it provides the class name in the form of a pointer and a length as opposed to a NULL-terminated copy. I figured this wouldn't be too difficult to change. In particular, my idea was to take the pointer to the type name and length that CTTI provides, copy the contents into a statically stored constexpr std::array and store a pointer into said array within the type info container.

Now, I'm neither completely foreign nor a genius when it comes to template metaprogramming, but the fact is that after a few hours I'm staring at an "internal error" and am somewhat out of ideas.

First the issue at hand. nameof.hpp contains the apply() function, which I've modified in the following way:


namespace ctti {
	namespace detail {
       	// this sub-detail snippet is mine...
		namespace detail {
			// the idea is to convert a pointer/string into a std::array
            template<std::size_t... Is>
            constexpr auto make_elements(const char* src, std::index_sequence<Is...>) -> std::array<char, sizeof...(Is)>
            {
                return std::array<char, sizeof...(Is)> {{ src[Is]... }};
            }

        template<typename T, typename = void>
        struct nameof_impl
        {
            static constexpr ctti::detail::cstring apply() {

              	// get the name as ctti currently does it...
                static constexpr const ctti::detail::cstring cst = ctti::detail::filter_typename_prefix(
                            ctti::pretty_function::type<T>().pad(
                                CTTI_TYPE_PRETTY_FUNCTION_LEFT,
                                CTTI_TYPE_PRETTY_FUNCTION_RIGHT
                            ));

                // the following is code I added (all of the involved functions are constexpr, so there should be no problem with compile time evaluation)
              
              	// get the length of the type name
                static constexpr const std::size_t N = cst.length();
              	// copy the substring into an array and store it as a static member within this function for any type T
                static constexpr const std::array<char, N> arr = detail::make_elements(cst.begin(), std::make_index_sequence<N>()); 
              	// get the pointer to the name
                static constexpr const char const* arrData = arr.data();

              	// construct a new ctti string that contains a pointer to the to-be NULL-terminated name
                return ctti::detail::cstring(cst.begin(), cst.length(), arrData);
         }
    }
}

Note that I haven't gotten to NULL-terminating the array yet. Not entirely sure how to do it, but first things first.

The problem arises when using arr (eg accessing the data pointer), which causes Visual Studio to presumably not optimize it and spit out a C1001 (internal compiler error). What am I doing wrong in my code? Is this a problem with the compiler or am I doing something inherently illegal?

 

 

NOTE: I've modified ctti::detail::cstring to contain the additional pointer (not shown here).

NOTE 2: Visual Studio currently chokes when using more than one template parameter pack, so in case you want to use the library, there's a modification that needs to be done in meta.hpp:

Spoiler


// near the very end of the file, foreach() will fail. The replacement code that wraps the parameter packs might look something like this:

        namespace detail {
            template<typename Function, typename Ts, typename Is>
            struct									foreach;

            template<typename ...Ts> struct TList	{ };
            template<std::size_t ...Is> struct IList { };

            template <
                typename Function,
                template<typename ...> class TList, typename ...Ts,
                template<std::size_t ...> class IList, std::size_t ...Is >
            struct foreach <Function, TList<Ts...>, IList<Is...> >
            {
                foreach(
                        Function function,
                        ctti::meta::list<Ts...>,
                		ctti::meta::index_sequence<Is...>) {
                	(void)function;
                    [](...) {}(std::array < int, sizeof...(Ts) + 1 > {{(function(ctti::meta::identity<Ts>(), ctti::meta::size_t<Is>()), 0)..., 0}});
                	}
            };
        }

        template<typename Sequence, typename Function>
        void										foreach(
            Function								function)
        {
            ctti::meta::detail::foreach(function,
                                        detail::TList<ctti::meta::apply_functor<ctti::meta::list<>, Sequence>()>,
                                        detail::IList<ctti::meta::make_index_sequence_from_sequence<Sequence>()>);
        }

 

 

NOTE 3: Additionally, by default CTTI will produce bad type information if you're using it with classes (as opposed to structs). To fix this, the following change needs to be made:

 

Spoiler


// in pretty_function.hpp, a define needs to be changed. Making the type shorter (struct->class) fixes the compile time
// parser and will recognize delimiters properly. Otherwise you might see something like "lass MyClassName[...]" as the
// type name.

...
#elif defined(_MSC_VER)
//#define CTTI_TYPE_PRETTY_FUNCTION_PREFIX "struct ctti::detail::cstring __cdecl ctti::pretty_function::type<"  // <- from this
#define CTTI_TYPE_PRETTY_FUNCTION_PREFIX "class ctti::detail::cstring __cdecl ctti::pretty_function::type<"  // <- to this
#define CTTI_TYPE_PRETTY_FUNCTION_SUFFIX ">(void)"
#else
...

 

 

You have seen the example on there GitHub page?


struct Foo
{
    int i;
};

constexpr ctti::name_t FooName = ctti::detailed_nameof<CTTI_STATIC_VALUE(&Foo::i)>();

int main()
{
    std::cout << FooName.name();                  // prints "i"
    std::cout << FooName.full_name();             // prints "&Foo::i"
    std::cout << FooName.full_homogeneous_name(); // prints "Foo::i"
    std::cout << FooName.qualifier(0);            // prints "Foo"
}

I personally prefer the CTTI/RTTI variant of the naming operator as it is more flexible (could be used with pointers that inherit/compose the GetType function)
 vua __PRETTY_FUNCTION__ trick


template<typename T> inline char* N()
	{
		static char type[sizeof(SIGNATURE) - SIGNATURE_LEFT - SIGNATURE_RIGHT] = { 0 };
					
		if(!type[0])
		{
			impl::TypeName::Lock();
			if(type[0])
			{
				impl::TypeName::Release();
				return type;
			}

			Drough::memcpy(type, SIGNATURE + SIGNATURE_LEFT, sizeof(SIGNATURE) - SIGNATURE_LEFT - SIGNATURE_RIGHT - 1);
			impl::TypeName::Release();
		}
		return type;
}
template <typename T> struct GetTypeName
{
	public:
		static inline const char* Name()
		{ 
			const char* typeName = FullName();

			static byte offset = 0;
			if(!offset)
			{
				impl::TypeName::Lock();
				if(offset) 
				{
					impl::TypeName::Release();
					return (typeName + (offset - 1));
				}

				const char* tmp = typeName;
				for(; *tmp; tmp++)
				{
					if(*tmp == ':')
						offset = static_cast<byte>(tmp - typeName);
				}
				if(offset > 0) offset++;
				offset++;
				impl::TypeName::Release();
			}
			return (typeName + (offset - 1));
		}
		static inline const char* FullName() { return impl::N<T>(); }
};

 

Advertisement

Hey - that's pretty neat. I became so focused on splicing the name up at compile time that I never considered filling it in at runtime :D. I like your approach a lot - thanks!

This topic is closed to new replies.

Advertisement