What is the difference between trait and politics?

I have a class whose behavior I'm trying to customize.

template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits; 

Then later I have my server object:

 template<typename TraitsT> class Server {...}; 

My question is that my use above is my naming wrong? Is my template parameter actually a policy, not a feature?

When is a template argument different from a policy?

+57
c ++ design typetraits template-meta-programming
Feb 05 '13 at 22:16
source share
4 answers

Policy

Policies are classes (or class templates) for bringing behavior into the parent class, usually through inheritance. By decomposing the parent interface into orthogonal (independent) dimensions, policy classes form the building blocks of more complex interfaces. A frequently viewed template is to provide policies as user-defined templates (or template templates) with a default library provided. An example from the standard library is Allocators, which are policy template settings for all STL containers.

 template<class T, class Allocator = std::allocator<T>> class vector; 

Here, the Allocator template parameter (which itself is also a class template!) Introduces a memory allocation and deallocation policy into the parent class std::vector . If the user does not provide a distributor, the default value is std::allocator<T> .

As is typical in pattern-based polymorphism, the interface requirements for policy classes are implicit and semantic (based on real expressions), rather than explicit and syntactic (based on the definition of virtual member functions),

Note that there are more than one policy in later unordered associative containers. In addition to the usual Allocator template parameter, they also adopt a Hash policy, which by default uses the function object std::hash<Key> . This allows users of unordered containers to configure them in several orthogonal sizes (memory allocation and hashing).

Traits

Traits are class templates for extracting properties from a generic type. There are two types of attributes: unambiguous traits and ambiguous traits. Examples of unique attributes are those from the <type_traits> header <type_traits>

 template< class T > struct is_integral { static const bool value /* = true if T is integral, false otherwise */; typedef std::integral_constant<bool, value> type; }; 

Unambiguous features are often used in metaprogramming and SFINAE pattern tricks to overload a function template based on a type condition.

Examples of multi-valued features are iterator_traits and allocator_traits from the <iterator> and <memory> headers, respectively. Because traits are class templates, they can be specialized. Below is an example of iterator_traits specialization for T*

 template<T> struct iterator_traits<T*> { using difference_type = std::ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; }; 

The expression std::iterator_traits<T>::value_type allows std::iterator_traits<T>::value_type to make common code for full-fledged iterator classes, suitable even for raw pointers (since the source pointers do not have a value_type member).

Interaction between policies and traits

When writing your own shared libraries, it is important to think about how users can specialize their own class templates. However, care must be taken to ensure that users do not fall prey to the One Definition Rule rule by using attribute specializations for injections rather than extracting behavior. Paraphrase this old post by Andrei Alexandrescu

The main problem is that code that does not see a specialized version of the attribute will still compile, most likely there will be a link, and sometimes it may even work. This is due to the fact that in the absence of explicit specialization, a non-specialized template is launched, probably implementing a general behavior that works for your particular case as Well. Therefore, if not all the code in the application sees the same sign definition, ODR is violated.

C ++ 11 std::allocator_traits avoids these errors by ensuring that all STL containers can only retrieve properties from their Allocator policies via std::allocator_traits<Allocator> . If users do not want or do not want to provide some of the required members of the policy, a feature class can be included and provide default values ​​for those absent members. Since allocator_traits itself cannot be specialized, users always need to go through a fully defined allocator policy in order to configure their memory allocation in containers, as well as prevent any ODR violations.

Note that you can still specialize feature class templates as a writer library (as STL does in iterator_traits<T*> ), but it is good practice to pass all user-defined specializations using policy classes to multi-valued features that a specialized one can extract behavior (as STL does in allocator_traits<A> ).

UPDATE . ODR problems of user-defined feature class specializations occur mainly when features are used as global class templates , and you cannot guarantee that all future users are all other user-defined specializations. Policies are local template parameters and contain all the relevant definitions, allowing them to be defined by the user without interference in another code. Local template parameters containing only type and constants, but not behavioral functions, can still be called β€œtraits”, but they will not be visible to other code, such as std::iterator_traits and std::allocator_traits .

+79
Feb 06 '13 at 7:47
source share

I think you will find the best answer to your question in this book by Andrei Alexandrescu . Here I will try to give a brief overview. Hope this helps.




A feature class is a class that is typically designed for a meta function that associates types with other types or with constant values ​​to provide a characterization of these types. In other words, this is a way to model type properties. A mechanism typically uses templates and specialized specialization to define an association:

 template<typename T> struct my_trait { typedef T& reference_type; static const bool isReference = false; // ... (possibly more properties here) }; template<> struct my_trait<T&> { typedef T& reference_type; static const bool isReference = true; // ... (possibly more properties here) }; 

The meta my_trait<> attribute my_trait<> above associates the reference type T& and the constant Boolean value false to all types T that are not links themselves; on the other hand, it binds the reference type T& and the constant Boolean value true to all types T that are references.

So for example:

 int -> reference_type = int& isReference = false int& -> reference_type = int& isReference = true 

In the code, we could state the following: (all four lines below will be compiled, which means that the condition expressed in the first argument of static_assert() is satisfied):

 static_assert(!(my_trait<int>::isReference), "Error!"); static_assert( my_trait<int&>::isReference, "Error!"); static_assert( std::is_same<typename my_trait<int>::reference_type, int&>::value, "Error!" ); static_assert( std::is_same<typename my_trait<int&>::reference_type, int&>::value, "Err!" ); 

Here you can see that I used the standard std::is_same<> template, which itself is a meta function that takes two, not one type argument. Everything can be complicated here.

Although std::is_same<> is part of the type_traits header, some consider a class template as a class of type attributes only if it acts as a meta predicate (thus, accepting one template parameter). However, to my knowledge, the terminology is not clearly defined.

For an example of using a feature class in a C ++ standard library, consider how to create an I / O library and a string library.




A policy is something a little different (in fact, quite another). Usually this is a class that indicates that the behavior of another generic class should be associated with certain operations that could potentially be implemented in several ways (and the implementation of which, therefore, is left to the policy class).

For example, a generic class of smart pointers can be designed as a template class that accepts a policy as a template parameter for deciding how to handle reference counting - this is just a hypothetical, overly simplified and illustrative example, so please try to distract from this specific code and focus on the mechanism.

This would allow the developer of the smart pointer not to make a hard-coded obligation as to whether changes to the reference counter should be performed in a thread-safe manner:

 template<typename T, typename P> class smart_ptr : protected P { public: // ... smart_ptr(smart_ptr const& sp) : p(sp.p), refcount(sp.refcount) { P::add_ref(refcount); } // ... private: T* p; int* refcount; }; 

In a multi-threaded context, the client can use an instance of the smart pointer template with a policy that implements thread-safe increments and reductions in the reference counter (it is assumed that the Windows platform is assumed here):

 class mt_refcount_policy { protected: add_ref(int* refcount) { ::InterlockedIncrement(refcount); } release(int* refcount) { ::InterlockedDecrement(refcount); } }; template<typename T> using my_smart_ptr = smart_ptr<T, mt_refcount_policy>; 

In a single-threaded environment, on the other hand, the client can instantiate a smart pointer template with a policy class that simply increments and decrements the counter value:

 class st_refcount_policy { protected: add_ref(int* refcount) { (*refcount)++; } release(int* refcount) { (*refcount)--; } }; template<typename T> using my_smart_ptr = smart_ptr<T, st_refcount_policy>; 

Thus, the library developer has provided a flexible solution that can offer the best compromise between performance and security ("You do not pay for what you do not use").

+22
Feb 05 '13 at 23:20
source share

If you use ModeT, IsReentrant and IsAsync to control the behavior of the Server, then this is the policy.

Alternatively, if you need a way to describe the characteristics of a server for another object, you can define a feature class as follows:

 template <typename ServerType> class ServerTraits; template<> class ServerTraits<Server> { enum { ModeT = SomeNamespace::MODE_NORMAL }; static const bool IsReentrant = true; static const bool IsAsync = true; } 
+3
Jun 17 '13 at 23:11
source share

Here are a few examples to clarify Alex Chamberlain's comment:

A common example of a feature class is std :: iterator_traits. Let's say we have some class C templates with a member function that takes two iterators, iterates over the values ​​and somehow accumulates the result. We want the accumulation strategy to be defined as part of the template, but to achieve this, we will use a policy, not a trait.

 template <typename Iterator, typename AccumulationPolicy> class C{ void foo(Iterator begin, Iterator end){ AccumulationPolicy::Accumulator accumulator; for(Iterator i = begin; i != end; ++i){ std::iterator_traits<Iterator>::value_type value = *i; accumulator.add(value); } } }; 

The policy is passed to our template class, and the attribute is transferred from the template parameter. So you have more akin to politics. There are situations where the signs are more appropriate and where the policy is more appropriate, and often the same effect can be achieved using any method that leads to some debate about what is most expressive.

+1
Feb 05 '13 at 23:12
source share



All Articles