The creators of templates in C ++ laid the foundation for a whole direction for research and development: it turned out that the C ++ template language has Turing completeness , that is, metaprograms (programs designed to work at the compilation stage) C ++ is able to calculate everything computable . In practice, the power of templates is most often used in the description of generalized data structures and algorithms: STL ( Standard Template Library ) is a living example.variadic patterns, type_traits library, tuples and constexpr 's, metaprogramming has become more convenient and visual, opening the way to the realization of many ideas that previously could be implemented only with the help of extensions of a specific compiler or complex multi-storeyed macros.if X is a node and Y is in the left subtree of X , then Y.key ≤ X.key . If the node Y is in the right subtree of X , then X.key ≤ Y.key .It is understood that we are able to compare keys (a transitive order relation is set on a set of key values, that is, simply speaking, the operation is “less”) and to talk about equality of keys. In the implementation without loss of generality, we will operate with strict order inequalities, using only the operation "<" and "==", built on its basis from the relation
x = y \; \ Leftrightarrow \; \ ;! (x <y) \: \ & \ :! (y <x)
template <class U, class V> struct tuple_concat; template <class Tuple, class T> struct tuple_push; type_traits header file (as part of a type support library) containing a collection of useful meta-functions. All of them are patterns of structures, and after instantiation, they locally define some type or a numeric value constant (or nothing, as in the case of disconnecting the overload using std::enable_if ). We will adhere to the same design principles of meta-functions.int and float ) is a meaningless operation, so the basic pattern of these structures is not defined (this will prevent it from being instantiated for arbitrary types), and all the useful work is done in partial specializations : template <template <class...> class T, class... Alist, class... Blist> struct tuple_concat<T<Alist...>, T<Blist...>> { using type = T<Alist..., Blist...>; }; template <template <class...> class Tuple, class... Args, class T> struct tuple_push<Tuple<Args...>,T> { using type = Tuple<Args..., T>; }; tuple_concat :T ) with a variable number of arguments, and they were instantiated with the Alist and Blist parameter Blist , then locally define the pseudonym type as instantiated the version of the same template of class T with a concatenated list of arguments, i.e. T<Alist..., Blist...> .tuple_concat with two tuples of the same type (for example, with two std::tuple ), the type of the same tuple with the “stitched” list of arguments of the input tuples is determined inside the structure. Other instantiations of instantiation are simply not compiled (the compiler will not be able to infer types for the partial specialization defined above, and instantiating a common pattern will be impossible due to the absence of its body). Example: using t1 = std::tuple<char,int>; using t2 = std::tuple<float,double>; using t3 = std::tuple<char,int,float,double>; using conc = typename tuple_concat<t1,t2>::type; // using err = typename tuple_concat<int,bool>::type; // static_assert(std::is_same<t3,conc>::value, ""); // , tuple_push specialization tuple_push not be a big deal. Additionally, for convenience, we define the corresponding template aliases : template <typename U, typename V> using tuple_concat_t = typename tuple_concat<U,V>::type; template <typename Tuple, typename T> using tuple_push_t = typename tuple_push<Tuple,T>::type; typename tuple_concat<t1,t2>::type tuple_concat_t<t1,t2> to access type instead of the typename tuple_concat<t1,t2>::type .tuple header contains a definition (not a meta-) of the tuple_cat() ) function, which constructs a tuple by concatenating an unspecified number of std::tuple 'passed to it as arguments. An attentive reader may notice that tuple_concat can be more easily implemented by outputting the result type decltype(tuple_cat(...)) , however, firstly, the implementation obtained above is not limited to the type of the std::tuple , and secondly, it was warm-up exercise for the gradual immersion in more complex type arithmetic. /// - STL-like template<size_t number> struct num_t : std::integral_constant<size_t, number> {}; // template<size_t number> struct num_t { enum : size_t { value = number } }; num_t<0> , num_t<13> , num_t<42> , etc. No more than for convenience, we endow this structure with a static numeric value , which will allow us to explicitly get back a number from the template argument (by accessing some_num_type::value ) without resorting to type inference.WOOD: NIL | [TREE, DATA, TREE]
Comp ( comparator , let 's talk about it further). /// struct NIL {}; /// template<typename T, typename Left, typename Right, typename Comp> struct node { using type = T; // node value using LT = Left; // left subtree using RT = Right; // right subtree using comp = Comp; // types comparator }; /// : ( ) template <typename T, typename Comp> using leaf = node<T,NIL,NIL,Comp>; template<...> struct one { struct two; using three = one<two, ...>; struct two : one<three, ...> {}; }; Note: it was experimentally found that such structures are compiled and instantiated by modern gcc and clang, however, I have not yet verified the strict adherence to the standard for declarations of such unusual patterns.However, in practice it is very, VERY difficult to work with such entities and create them. The reciprocal reference to the parent element produces an interesting effect: in fact, our "simply connected tree" turns into a real graph (tasty!), Which, with any modification, must instantiate itself "at a time", again and completely (sadly). A deeper analysis of this situation is beyond the scope of this article and is among my future plans for exploring the possibilities of metaprogramming in C ++.
Comp structure must contain type comparison metafunctions (i.e., less than and equal to operation patterns). Let us write an example of such a comparator based on the sizeof 'ah types (perhaps, the only numerical characteristic defined for all full types): struct sizeof_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, (sizeof(U) < sizeof(V))> {}; // template <typename U, typename V> struct eq : std::integral_constant<bool, (sizeof(U) == sizeof(V))> {}; // }; lt is a “less” metafunction for types, eq is an “equal” metafunction. The approach shown earlier for determining the types of numbers was used: inheritance from std::integral_constant will endow the instantiated lt and eq static Boolean values. struct num_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, (U::value < V::value)> {}; template <typename U, typename V> struct eq : std::integral_constant<bool, (U::value == V::value)> {}; }; value . /// template <typename lt_traits> struct eq_comp { template <typename U, typename V> struct lt : std::integral_constant<bool, lt_traits::template lt<U,V>::value> {}; template <typename U, typename V> struct eq : std::integral_constant<bool, (!lt<U,V>::value && !lt<V,U>::value)> {}; }; /// sizeof, eq_comp::eq struct sizeof_comp : public eq_comp<sizeof_comp> { template <typename U, typename V> struct lt : std::integral_constant<bool, (sizeof(U) < sizeof(V))> {}; }; /// num_t struct num_comp : public eq_comp<num_comp> { template <typename U, typename V> struct lt : std::integral_constant<bool, (U::value < V::value)> {}; }; The size of the definition has decreased and a mathematical background has appeared - isn't it beautiful? using t1 = node< num_t<5>, node< num_t<3>, leaf<num_t<2>>, leaf<num_t<4>> >, node< num_t<7>, NIL, leaf<num_t<8>> > >; Note: Here and hereinafter in the examples, the default is to comparenum_comp, explicitly indicating it in the list of template arguments is omitted. In general, after the development of theinsertoperation, we will not have to build trees in this way (by definition).

printf/std::cout . There are techniques that allow typing readable inferred types inside the compiler error messages, and, in principle, this is quite a useful feature for checking generated structures (for example, after modifying a tree).type_traits can be simply printed as typeid(t).name() (starting from C ++ 11, you can legally look into RTTI). Simple data structures can be displayed on the screen with special metafunctions with tail recursion; for complex ones, printers will have to be built, comparable in complexity to operations on the structure itself. / - {2}
/ - {3} - <
\--{four}
- {5} --- <
\ - {7} - \
\--{eight}
/// Input: T - tree, exit: h - height
HEIGHT (T):
IF T == NIL
OUTPUT 0
ANYWAY
OUTPUT 1 + MAX (HEIGHT (T.LEFT), HEIGHT (T.RIGHT))
/// template <typename Tree> struct height; /// : " T == NIL" template <> struct height<NIL> : std::integral_constant<size_t, 0> {}; /// () template <typename Tree> struct height { static constexpr size_t value = 1 + max( height<typename Tree::LT>::value, height<typename Tree::RT>::value ); }; Note: we deliberately went for a small simplification, because of which the calculated tree height will be 1 more than its mathematical definition (the height of the empty set is not defined). , 1 , height .: height , value = 0 , , ( NIL ), . C++: , ( ), ( Tree::LT , Tree NIL ).max . constexpr ( , ), : template <typename T> constexpr T max(T a, T b) { return a < b ? b : a; } height : static_assert(height<t1>::value == 3, ""); height : n , n — ; h — .. .constexpr - variadic- .height gcc, > 900. O - ( ), .std::integer_sequence ): N 0..N-1, , . , , ( , 30- 9000- ).
/// Input: T - tree, output: w - list of data from in-order bypass nodes
INORDER (T):
IF T == NIL
CONCLUSION {}
ANYWAY
INORDER(T.LEFT) + {T.KEY} + INORDER(T.RIGHT)
/// template <typename Tree> struct walk; /// : " T == NIL" template <> struct walk<NIL> { using type = std::tuple<>; }; /// () template <typename Tree> struct walk { private: // using accL = typename walk<typename Tree::LT>::type; // using accX = tuple_push_t<accL, typename Tree::type>; // using accR = tuple_concat_t<accX, typename walk<typename Tree::RT>::type>; public: // using type = accR; }; private : , , . , ?walk : O(n) , n — ( O - : 3n ). , tuple_concat tuple_push 1 , ( parameter pack '). , height , h — .Comp . :/// : T - , k - -, /// : N - , k` == k
SEARCH(T,k):
T == NIL k == T.KEY
T
k < T.KEY
SEARCH(T.LEFT, k)
SEARCH(T.RIGHT, k)
/// template <typename Tree, typename T> struct search; /// template <typename T> struct search<NIL,T> { using type = NIL; }; /// template <typename Tree, typename T> struct search { using Comp = typename Tree::comp; using type = typename std::conditional< Comp::template eq<T, typename Tree::type>::value, // k == T.KEY ? Tree, // , : typename std::conditional< Comp::template lt<T, typename Tree::type>::value, // k < T.KEY ? typename search<typename Tree::LT, T>::type, // typename search<typename Tree::RT, T>::type // -- >::type >::type; }; Tree::comp , : , ( , ), ( node<> ).Tree::comp::eq<...> Tree::comp::lt<...> ), template .std::conditional — , ( ?: ). — . , — .search — - O(n) , — h ( ). «!» — , — « ?»std::conditional ?: : , (, , ). std::conditional ( ), std::conditional , . using found3 = search_t<NIL, num_t<0>, num_comp>; // using found4 = search_t<t1, num_t<5>, num_comp>; // using found5 = search_t<t1, num_t<8>, num_comp>; // static_assert(std::is_same<found3, NIL>::value, ""); // static_assert(std::is_same<found4, t1>::value, ""); // static_assert(std::is_same<found5, leaf<num_t<8>>>::value, ""); // std::map) trees are also stored in the nodes of the pair ( std::pair), and the first element of the pair is considered the key , which, in fact, participates in comparisons. It is enough to keep the same in our tree std::pairand make the comparator Compcompare pairs of the first type in a pair - and get a classic associative (meta) container! We will return to this idea at the end of the article./// Input: T is a tree, k is a key type for insertion, /// exit: T 'is a new tree with an inserted element
INSERT (T, k):
IF T == NIL
CONCLUSION {NIL, k, NIL}
ANYWAY
IF k <T.KEY
OUTPUT {INSERT (T.LEFT, k), T.KEY, T.RIGHT}
ANYWAY
CONCLUSION {T.LEFT, T.KEY, INSERT (T.RIGHT, k)}
template <typename Tree, typename T, typename Comp = typename Tree::comp> struct insert; /// template <typename T, typename Comp> struct insert<NIL,T,Comp> { using type = leaf<T,Comp>; }; /// template <typename Tree, typename T, typename Comp> struct insert { using type = typename std::conditional< Comp::template lt<T, typename Tree::type>::value, // k < T.KEY? node<typename Tree::type, // {INSERT(T.LEFT,k), T.KEY, T.RIGHT} typename insert<typename Tree::LT, T, Comp>::type, typename Tree::RT, Comp>, node<typename Tree::type, // {T.LEFT, T.KEY, INSERT(T.RIGHT,k)} typename Tree::LT, typename insert<typename Tree::RT, T, Comp>::type, Comp> >::type; }; Comp ; , - * . using t2 = leaf<num_t<5>, num_comp>; using t3 = insert_t<t2, num_t<3>>; using t4 = insert_t<t3, num_t<7>>; static_assert(height<t4>::value == 2, ""); // 2 using t5 = insert_t<insert_t<insert_t<t4, num_t<2>>, num_t<4>>, num_t<8>>; static_assert(std::is_same<t5, t1>::value, ""); // insert_tuple , ( insert ), : using t6 = insert_tuple_t<NIL, std::tuple< num_t<5>, num_t<7>, num_t<3>, num_t<4>, num_t<2>, num_t<8> >, num_comp>; static_assert(std::is_same<t1, t6>::value, ""); 
/// : T - , l - , /// : t -
COLLECT_LEVEL(T,l):
T == NIL
{}
l == 0
{T.KEY}
COLLECT_LEVEL(T.LEFT,l-1) + COLLECT_LEVEL(T.RIGHT,l-1)
collect_level ).
std::tuple — . , runtime , , ( std::get std::tuple ).std::map ( std::set ) — , (, ) ( — , ..), compile-time, runtime . : , , . : ?static_assert , std - ( — tutorial ). , .std::conditional . search O(h) : template <typename Node, typename T, typename Comp> struct search { using type = typename std::conditional< Comp::template eq<T, typename Node::type>::value, Node, typename search<typename std::conditional< Comp::template lt<T, typename Node::type>::value, typename Node::LT, typename Node::RT >::type, T, Comp>::type >::type; }; (+ insert ) .insert search remove O(h) ( insert ). SFINAE + decltype ( , . ). remove : template <typename Tree, typename T> struct remove { private: enum : bool { is_less = Tree::comp::template lt<T, typename Tree::type>::value }; enum : bool { is_equal = Tree::comp::template eq<T, typename Tree::type>::value }; enum : size_t { children = children_amount<Tree>::value }; using key = typename min_node_t< typename std::conditional< is_equal && children == 2, typename Tree::RT, leaf<typename Tree::type, typename Tree::comp> >::type >::type; using recursive_call = typename remove< typename std::conditional< is_less, typename Tree::LT, typename std::conditional< !is_equal || children == 2, typename Tree::RT, NIL >::type >::type, typename std::conditional< is_equal, key, T >::type >::type; static typename Tree::RT root_dispatcher(...); template <typename Bush> static typename std::enable_if< sizeof(typename Bush::LT::type), typename Bush::LT >::type root_dispatcher(Bush&&); public: using type = typename std::conditional< is_equal && (children < 2), decltype(root_dispatcher(std::declval<Tree>())), node< key, typename std::conditional< is_less, recursive_call, typename Tree::LT >::type, typename std::conditional< !is_less, recursive_call, typename Tree::RT >::type, typename Tree::comp > >::type; }; , , . , , , , .Source: https://habr.com/ru/post/320686/
All Articles