C + + type system enhancements

Keywords: Programming network

You should be familiar with it. In C + +, you can get the type name through the operator typeid

std::cout << typeid(int).name() << std::endl;

But the return value of name() is Compiler dependent The results printed in vc and gcc are as follows:

int // vc
i   // gcc

For custom classes, the output type is not the original type

class TestType {};
std::cout << typeid(TestType).name() << std::endl;

//vs output class TestType

Therefore, the function of the operator typeid is limited to type output and type to type comparison, which cannot be used for string comparison.

Note: the following is only for vc compiler

Let's look at the output of the container type:

std::cout << typeid(std::map<std::string, int>).name() << std::endl;

The following will customize the type processing, encapsulate the typeid, and simplify the type output

Define type processing template

//Non specialized process, automatic extraction and classification
template<typename T>
struct type_system
{
	std::string name()
	{
		return type_tarits<T>().name();
	}
};

std::string processing

// std::string supported
template<>
struct type_system<std::string>
{
	std::string name()
	{
		return "std::string";
	}
};

Container type handling for a single parameter

// STL container supporting one parameter
#define __TYPE_SYSTEM_DEF_ONE(OPT) \
template<typename T> \
struct type_system< OPT<T> > \
{ \
	std::string name() \
	{\
		std::ostringstream oss;\
		oss << #OPT  << "<" << type_system<T>().name()  << ">";\
		return oss.str();\
	}\
};

__TYPE_SYSTEM_DEF_ONE(std::vector)

Container type handling of two parameters

// STL container supporting two parameters
#define __TYPE_SYSTEM_DEF_TWO(OPT) \
template<typename K, typename V> \
struct type_system< OPT<K, V> >\
{\
	std::string name()\
	{\
		std::ostringstream oss;\
		oss << #OPT << "<" << type_system<K>().name() << "," << type_system<V>().name() << ">";\
		return oss.str();\
	}\
};

__TYPE_SYSTEM_DEF_TWO(std::map)

Support std::tuple output

struct TupleArgs
{
	template<typename Tuple, std::size_t... I>
	static void Get(std::ostringstream& oss, std::index_sequence<I...>)
	{
		std::vector<std::string> paramType{ _Get<typename std::tuple_element<I, Tuple>::type>()... };
		for (size_t i = 0; i < paramType.size(); i++)
		{
			oss << paramType[i];
			if (i != paramType.size() - 1)
			{
				oss << ",";
			}
		}
	}

	template<typename T>
	static std::string _Get()
	{
		return type_system<T>().name();
	}
};

// std::tuple support
template<typename...Args> 
struct type_system< std::tuple<Args...> >
{
	std::string name()
	{
		std::ostringstream oss; 
		oss << "std::tuple" << "<";
		TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
		oss << ">";
		return oss.str(); 
	}
};

Support function pointer

// Support function pointer
template<typename R, typename...Args>
struct type_system< R (*)(Args...) >
{
	std::string name()
	{
		std::ostringstream oss;
		oss << type_system<R>().name() << "(*)(";
		TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
		oss << ")";
		return oss.str();
	}
};

Support member variables

// Support member variables
template<typename T, typename C>
struct type_system<T C::* >
{
	std::string name()
	{
		std::ostringstream oss;
		oss << type_system<T>().name() << " " << type_system<C>().name() << "::*";

		return oss.str();
	}
};

Support member function pointer

template<typename R, typename C, typename...Args>
struct type_system< R(C::*)(Args...) >
{
	std::string name()
	{
		std::ostringstream oss;
		oss << type_system<R>().name() << "(" << type_system<C>().name() << "::*)(";
		TupleArgs::Get<std::tuple<Args...>>(oss, std::make_index_sequence<sizeof...(Args)>{});
		oss << ")";
		return oss.str();
	}
};

There are specialized types, basic types and user-defined types, which need to be further differentiated

//Type system
template<typename T, typename P>
struct __type_system;

//Non class type
template<typename T >
struct __type_system<T, typename  std::enable_if<!std::is_class<T>::value, T>::type>
{
	std::string name()
	{
		return typeid(T).name();
	}
};


//Class type
template<typename T >
struct __type_system<T, typename  std::enable_if<std::is_class<T>::value, T>::type>
{
	std::string name()
	{
		std::string tmpName = typeid(T).name();
		int nPos = tmpName.find("class ");
		if (nPos != std::string::npos)
		{
			return tmpName.substr(nPos + 6);
		}
		return tmpName;
	}
};

template<typename T>
class type_tarits : public __type_system< T,  T>
{
public:
};

All types are partitioned and processed above. Next, a common function template is defined to simplify the use. Parameter value and parameter type can be obtained in two ways

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

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

Next, test the output effect:

int main()
{
	std::cout << type_name<int>() << std::endl;
	std::cout << type_name<std::map<std::string, std::string>>() << std::endl;
	std::cout << type_name<std::map<std::string, std::vector<std::string>>>() << std::endl;
	std::cout << type_name<Test>() << std::endl;

	std::unordered_map<std::string, std::string> v1;
	std::tuple<std::string, std::string, int, double> t1;
	std::cout << type_name(v1) << std::endl;
	std::cout << type_name(t1) << std::endl;

	std::cout << type_name(&FuncTest) << std::endl;
	std::cout << type_name(&Test::Func) << std::endl;
	std::cout << type_name(&Test::a) << std::endl;	
    return 0;
}

 

Summary:

Because the project needs to reverse output the type, because it's interesting to pause and study the type system. The above content uses a lot of generic programming skills, making full use of the processing power of C + + in the preprocessing, compilation and runtime (RAII) On this basis, we can make progress to refine the processing, and can also carry out word parsing processing on different platforms, which has achieved the goal of unified platform.

You may ask, what's the use of studying these? In fact, it's not the rise of research and play here. My purpose is to output the client code in the way of self analysis of the server in network programming, which has ensured the consistency of the interface between the client and the server, and simplified the development process.

    At present, in RPC framework, most of the interfaces are generated by defining interface files, and then through the interface resolver. However, it is very tedious for the interface code of the server to inherit into the service. If the server can self resolve and generate the client code, it is as simple as developing a stand-alone program when facing network programming. How interesting it is What an exciting thing!

Posted by evaoparah on Sun, 14 Jun 2020 00:42:23 -0700