Fun with templates! Reflection through specialization

Hi all,

I'm working on a home project where I'm trying to create a lean reflection layer using RTTI and templating.

At the moment I've got most of it running but some things didn't sit well with me, especiallly since I think it can be improved by smarter people than myself :)

Ok, so here goes: I have/need a single function that can inspect a static type and give usefull information about it. I use this to populate my reflection layer from code without the use of too much macro's and duplication.

So my simplified use case is as follows:
1
2
std::vector<std::vector<float*>*> test;
InspectObject( &test );

should (and does) return the string POINTER TO VECTOR OF POINTER TO VECTOR OF POINTER TO FLOAT
I'm using std vectors atm but the same goes for any type of templated container.

I made this work by defining the InspectObject as a set of templated functions:
1
2
3
4
5
6
template <typename T> void InspectObject(T& inDummy)				{ std::wcout << L"GENERIC" << std::endl; }
template <> void InspectObject<int>(int& inDummy)					{ std::wcout << L"INT" << std::endl; }
template <> void InspectObject<float>(float& inDummy)				{ std::wcout << L"FLOAT" << std::endl; }
template <> void InspectObject<bool>(bool& inDummy)					{ std::wcout << L"BOOL" << std::endl; }
template <typename T> void InspectObject(T* inDummy)				{ std::wcout << L"POINTER TO" << std::endl; InspectObject(*inDummy); }
template <typename T> void InspectObject(std::vector<T>& inDummy)	{ std::wcout << L"VECTOR OF" << std::endl; InspectObject(*((T*)0)); }

Ugly no? Because of the dummy objects passed in the function; they are never touched and used more as an additional layer of function-matching during template generation.

Ideally I would like the function to be more like:
 
InspectClass< std::vector<std::vector<float*>*> >();

returning the same data. This looks like it should be possible, I just get hung up on the syntax:

1
2
3
4
5
template <typename T> void Inspect()								{ std::wcout << L"GENERIC" << std::endl; }
template <> void Inspect<int>()										{ std::wcout << L"INT" << std::endl; }
template <> void Inspect<float>()									{ std::wcout << L"FLOAT" << std::endl; }
template <> void Inspect<bool>()									{ std::wcout << L"BOOL" << std::endl; }
template <> template <typename C> void Inspect< std::vector>()		{ std::wcout << L"VECTOR OF" << std::endl; Inspect<C>(); }


For the life of me I can't get that last case to compile properly.
Any thoughts?

Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <string>
#include <typeinfo>

#ifdef __GNUG__

#include <cxxabi.h>
#include <cstdlib>
#include <memory>

template< typename T > std::string type_name()
{
    int status ;
    std::unique_ptr< char[], decltype(&std::free) > buffer(
        __cxxabiv1::__cxa_demangle( typeid(T).name(), nullptr, 0, &status ), &std::free ) ;
    return status==0 ? buffer.get() : "__cxa_demangle error" ;
}

#else // !defined __GNUG__

template< typename T > std::string type_name()
{
    return typeid(T).name() ;
}

#endif //__GNUG__

template< typename T > std::string type_name( const T& )
{
    return type_name<T>() ;
}

#include <type_traits>
#include <iostream>
#include <vector>

template < typename T, typename = void > struct inspector
{ void operator() () const { std::cout << type_name<T>() << '\n' ; } };

template < typename T > struct inspector<T*,void>
{ void operator() () const { std::cout << "pointer to " ; inspector<T>{}() ; } };

template < typename T > struct inspector<T&,void>
{ void operator() () const { std::cout << "lvalue reference to " ; inspector<T>{}() ; } };

template < typename T > struct inspector<T&&,void>
{ void operator() () const { std::cout << "rvalue reference to " ; inspector<T>{}() ; } };

template < typename T > struct inspector< std::vector<T>, void >
{ void operator() () const { std::cout << "vector of " ; inspector<T>{}() ; } };

// etc.

template < typename T > void Inspect() { inspector<T>{}() ; }

int main()
{
   Inspect<float>() ; //  float
   Inspect<float*>() ; // pointer to float
   Inspect< std::vector<float> >() ; // vector of float
   Inspect< std::vector<float*> >() ; // vector of pointer to float
   Inspect< std::vector<float>* >() ; // pointer to vector of float
   Inspect< std::vector<float*>& >() ; // lvalue reference to vector of pointer to float
   Inspect< std::vector< std::vector<float>* >&& >() ; // rvalue reference to vector of pointer to vector of float
   
   std::vector< std::vector<float*> >* object = nullptr ;
   Inspect< decltype(object) >() ; // pointer to vector of vector of pointer to float
   Inspect< decltype( (object) ) >() ; // lvalue reference to pointer to vector of vector of pointer to float
   Inspect< decltype( std::move(object) ) >() ; // rvalue reference to pointer to vector of vector of pointer to float
}

http://coliru.stacked-crooked.com/a/91c406780be0429d

A lot of the information that you want is available through the <type_traits> header.
http://en.cppreference.com/w/cpp/header/type_traits
This looks exactly like what I mean.
I'll piece it together when I get home.

I actually looked into the reason why your solution with functors works, but my solution with templated functions does not. I'm afraid I do not really know.

I removed the void parameter, and it seems to still compile and function. The difference with the template functions is that the compiler seems to break on the fact that there are two template<typename X> functions of the same name, one of which is partially specialized. Is this a limitation of function templates specifically?

Thanks a million!
Last edited on
@ JLBorges: I just want to say that Lines 10 - 16 brought a smile to my face. It's always entertaining to read your work.
> I removed the void parameter, and it seems to still compile and function.

Yes. In the current code, that parameter is unused.
It was put in there to provide for specialisations with SFINAE in future. For instance:
1
2
3
template < typename T > 
struct inspector< T, typename std::enable_if< std::is_trivial<T>::value, void >::type > 
{ /* ... */ };



> The difference with the template functions is that the compiler seems to break
> on the fact that there are two template<typename X> functions of the same name,
> one of which is partially specialized. Is this a limitation of function templates specifically?

Yes. The language does not allow function templates to be partially specialised.

In general, avoid specialising function templates. http://www.gotw.ca/publications/mill17.htm
I get it - I didn't even thought of that.
You really know your stuff. Thanks again :)
Topic archived. No new replies allowed.