📜 ⬆️ ⬇️

Universal Auto Designer

With the advent of C ++ 11, it became possible to declare variables of the auto type, and the compiler itself determined the actual type of the variable, based on the type of value being initialized. This is useful when we want to initialize a variable whose type is too complex, or unknown, or it is not very important to us, or simply for simplicity.

For example:

auto f = [](){}; //   auto r = foo(10); //   foo for (auto i = 0; i < 10; i++){} 

… etc. That is, on the left side of the equality we have an automatic type auto, and on the right side we have the value of a well-defined type. Now imagine that we have the opposite:
')
 int a = auto(10); 

On the left we have a clearly described type, and on the right something unknown. Of course, in this example it makes no sense to call the universal constructor, when you could simply assign a value of 10 to the variable a:

 int a = 10; 

Or in extreme cases, call its constructor:

 int a(10); 

And if this is a function argument, for example:

 str::map<char, int> myMap; myMap.insert(pair<char, int>('a', 10)); 

The insert method of the template class map expects a well-defined type, but we have to write “pair <char, int>” again and again with each call. Well if our type is simple, and if there is a pattern on the template and the pattern chases? Here the automatic constructor will help us:

 myMap.insert(auto('a', 10)); 

A function, constructor or auto operator, no matter what it is, will create us some object that fits the description of the input parameter of the insert method.

But unfortunately, there is no such method for creating objects in C ++ yet, but I hope that someday it will appear, but for now I want to present my own version of the implementation of such a task. Of course, the main goal is to simplify the writing of code, but an equally important task is not to harm our program: it should not increase in volume, slow down in execution, etc. Ideally, it should be identical to the one that if we wrote without the auto constructor.

So. We need to create some kind of universal object that could be converted to the requested type and do it at the compilation stage. Of course, I do not take into account the optimization of compilation O0, Og, etc. take the Os optimization.

Our object (container) must accept all input arguments, save them, and when converting to the requested type, try to call its constructor with the transfer of everything that was once passed to it.

First we need a universal variable that can store both copies of objects, and pointers and links. Although the pointer and the copy are the same in this case, but with the link it is more difficult:

 template<typename T> struct Value { constexpr Value(T v): v(v) {} constexpr T get() {return v;} T v; }; template<typename T> struct Value<T&> { constexpr Value(T& v): v(&v) {} constexpr T& get() {return *v;} T* v; }; 

Everything is relatively simple here, we take any argument and save its copy, and in the case with the link we convert it to a pointer and back.

The specialization itself is necessary to weed out the arguments by reference in order to create universality, but without conversion to a pointer, the compiler refuses to perform at the compilation stage, although it would seem that there is a difference if we store in the pointer or in the link.

Now we need to create a universal container with an unlimited number of arguments of arbitrary types:

 template<typename... Types> struct Container { constexpr Container() {} template<typename T> constexpr operator T() {return get<T>();} template<typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);} }; template<typename Type, typename... Types> struct Container<Type, Types...> { constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {} template<typename T> constexpr operator T() {return get<T>();} template<typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());} Value<Type> arg; Container<Types...> args; }; 

A recursive container accepts an unlimited number of arguments of various types and places the first argument to itself, and the rest in a nested container with the remaining arguments, until we reach the last argument.

Also, this container has a conversion operator to any desired type by calling the recursive get method with nesting of all available arguments.

All arguments are passed as rvalue arguments to the final destination Value, so as not to lose references.

And finally, the universal constructor Auto itself. I called it with a capital letter, because The auto keyword, you know, is already taken. And given that this function serves as a constructor capital letter to her face.

 template<typename... Types> constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);} 

Finally, we move the Value class to the private area of ​​the Container class and we get the following:

 template<typename... Types> struct Container { constexpr Container() {} template<typename T> constexpr operator T() {return get<T>();} template<typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);} }; template<typename Type, typename... Types> struct Container<Type, Types...> { constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {} template<typename T> constexpr operator T() {return get<T>();} template<typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());} private: template<typename T> struct Value { constexpr Value(T v): v(v) {} constexpr T get() {return v;} T v; }; template<typename T> struct Value<T&> { constexpr Value(T& v): v(&v) {} constexpr T& get() {return *v;} T* v; }; Value<Type> arg; Container<Types...> args; }; template<typename... Types> constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);} 

All conversion is performed at the compilation stage and is no different from a direct call to the constructors.

True, there is a slight inconvenience - no prompter can offer you options for input arguments.

Source: https://habr.com/ru/post/316030/


All Articles