"Concepts" and Generic Programming

Started by
26 comments, last by Oluseyi 21 years, 9 months ago
in concept_check.h
  #define _STLP_REQUIRES(__type_var, __concept) \do { \  void (*__x)( __type_var ) = __concept##_concept_specification< __type_var >\    ::##__concept##_requirement_violation; __x = __x; } while (0)  


Which, for (VAR,CONCEPT) gives
  do {  void (*__x)( VAR ) =   CONCEPT_concept_specification<VAR>::CONCEPT_requirement_violation;   __x = __x; } while (0)  


Where CONCEPT_requirement_specification is a template class containing one function, CONCEPT_requirement_violation. That function (directly or indirectly by instanciating other function templates) contains code executing the operations that the concept is supposed to implement.

e.g. at the end of the instanciation chain ''Default Constructible'' is checked by
  template <class _Type>  static _Type  __default_constructor_requirement_violation(_Type) {    return _Type();  }  

which, if executed, would default-construct its templated type.

This relies on the fact that templates that are not used are not instanciated. The assignment of the function pointer __x to itself , while a no-op, is sufficient to cause the instanciation of the template. Which will fail to compile if the template parameter does not implement the required operations.

Normally, the compiler will recognize that the function pointer is never really used during its lifetime and will optimize it away. If not, you have a performance cost equivalent to the initialisation of a single local parameter and its self assignment : one MOV to set it to a constant (the address) and one intra-register to itself (though _that_ should be optimized). The functions actually referenced are never executed !

STLPort use a macro to enables or disable concept checks (_STLP_USE_CONCEPT_CHECKS) depending on the compiler.

I think that sums it all. Pretty nifty IMHO.
Boost also has a concept-check module to that effect.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Advertisement
quote:Original post by MadKeithV
The advantage of the above code over "normal" inheritance-only polymorphism is that the container now contains implementations that have nothing do to with eachother, class-wise, but simply provide an update method. ANYTHING that provides the Update method can be wrapped in the GenericUpdateClass - and with some extra work you can even Update classes that have a differently-named Update function - using either partial specialisation of the GenericUpdateClass, or deriving another class from BaseGenericUpdateClass.

This discussion is very interesting, but I feel quite dumb... I understand how MadKeithV''s example works, but I don''t understand the advantages.

I feel that "ANYTHING that provides the Update method" could also inherit from the abstract base class. What''s the big deal with the templated approach? It seems a lot more messy to me.

I agree with Magmai Kai Holmor''s comments
quote:
I just want to emphasize that templates are not a replacement for OO (nor vice-versa). They compliment eachother. Templates are for compile-time polymorphism, OO is for run-time polymorphism. You can emulate one using the other, but the results are horrendous in both cases.

Virtual functions seems (IMHO) the best and cleanest solution to the "heterogeneous containers" problem.
quote:By Oluseyi
[with GP,] the programmer does not have to locate her new class in the inheritance "family tree"

What do you mean? If I have access to CObject, I can derive from it wherever I want and create a new CObject.

Thanks for clearing this up for me. I guess what I would like is an explanation of those "practical gains in terms of code size and maintainability" that Oluseyi wrote about in his first post.

Cédric
First, let me thank Fruny for that expose on _STL__REQUIRES and pointers to Boost''s concept check module. I''ll delve more deeply into both ASAP.

quote:Original post by cedricl
I feel that "ANYTHING that provides the Update method" could also inherit from the abstract base class. What''s the big deal with the templated approach? It seems a lot more messy to me.

Runtime versus compile-time polymorphism. Many times we use virtual functions and inheritance merely to provide a consistent interface and call functions with impunity. However, in those instances we are required to pass a pointer to the object and rely on vtable replacement to locate the appropriate function. Consider: if we had three classes A, B and C and wished to write a function that operated on A, B and C, calling their respective Update methods (same signature) without using GP, we''d have a base class (say, X) and inherit the interface from X. Our function would then take a pointer to X and call ->Update() on that, which would work fine. It turns out in this case, however, that the object type is known at compile time, so that is redundant overhead and unnecessary inheritance.

Using GP, we merely have our function take the class type as a parameter (implict even) and call .Update() as if we knew the exact type. The complete code is generated at compile time, no problems. If we want different processing for different branches of the tree, we partially specialize the template funcion. Presto! Doing the same using inheritance might require RTTI and so forth.

quote:
I agree with Magmai Kai Holmor''s comments.

Absolutely. My original post stemmed from seeing people use, suggest and recommend inheritance for problems better solved by GP. There is no form of template manipulation that can replace dynamically selecting a renderer (eg DirectX or OGL) at runtime based on user input.

quote:
Virtual functions seems (IMHO) the best and cleanest solution to the "heterogeneous containers" problem.

It depends. If it''s merely interface inheritance for static usage, then I''m against it. If it''s any form of inheritance for runtime use, or any other form of runtime polymorphism, then it''s inheritance and virtual functions all the way.

quote:
What do you mean [by [with GP,] the programmer does not have to locate her new class in the inheritance "family tree"]? If I have access to CObject, I can derive from it wherever I want and create a new CObject.

Again, see above. The very name CObject seems to indicate overly broad inheritance schemes. using GP in conjunction with inheritance and polymorphism, the developer can achieve higher granularity and fewer design and implementation conflicts. Both tools are very useful.

quote:
I guess what I would like is an explanation of those "practical gains in terms of code size and maintainability" that Oluseyi wrote about in his first post.

Generally, generic algorithms are written type agnostic and with no assumptions as to internal data, emaning they''re usable with a wider variety of objects/types, provided the object/type conforms to the concept. That is why std::find works on all STL container iterators as well as raw pointers, and any other object that implements InputIterator semantics. This means writing less code for more objects, and only duplicating where specialization is needed.
quote:Original post by Oluseyi
Using GP, we merely have our function take the class type as a parameter (implict even) and call .Update() as if we knew the exact type. The complete code is generated at compile time, no problems. If we want different processing for different branches of the tree, we partially specialize the template funcion. Presto! Doing the same using inheritance might require RTTI and so forth.

Hmm... Function overloading is pretty much the equivalent of template specialization in the function world. You could specialize the same way you are doing with templates.

Otherwise, thanks for your post. It makes some sense, and I already use templates extensively to avoid virtual calls in critical situations, but I''m not sure that I see the complete potential of what is discussed here. I''ve got to think about it some more.

On another note, being able to remove ''virtuosity'', or being able to make a class truly final would allow the compiler to statically resolve the function calls at compile-time when the type is known, as was discussed in the General Programming forum a few weeks ago. Just a thought.

Cédric
quote:Original post by cedricl
Hmm... Function overloading is pretty much the equivalent of template specialization in the function world. You could specialize the same way you are doing with templates.


Yes. Templated functions are basically automatically-generated overloaded functions. It is nothing that you couldn''t do yourself, but with templates, you do not have to repeat the code in 20 (or more) different places, not make sure that you have an overload for each and every different type or combination of types (implicit conversions non-withstanding) that you may use in your program.

Generic Programming enables forward compatibility : STL algorithms will work even for types that were not considered when the library was designed.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
quote:Original post by cedricl
Otherwise, thanks for your post. It makes some sense, and I already use templates extensively to avoid virtual calls in critical situations, but I''m not sure that I see the complete potential of what is discussed here. I''ve got to think about it some more.


Observor pattern and to avoid MI.

There is an article on www.codeproject.com by "Daniel Lohmann" in the tips->Design and Strategy section.

I can''t post the link as the website is currently down. The article name is

"Observing the world - how to build a reuseable implementation of a design pattern"

quote:Original post by Void
I can't post the link as the website is currently down. The article name is
"Observing the world - how to build a reuseable implementation of a design pattern"


Google cache

And (for the observer pattern), you can use boost::signal (which is heavily templated on the possible types of function object you might pass).

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]


[edited by - Fruny on July 2, 2002 11:02:30 PM]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
quote:Void
... and to avoid MI.

I had to think about what was meant for a bit - not only could it avoid multiple inheritence, it can avoid any inheritence requirement at all.

In practice, MI is promoted, as you typically inherit from mutliple template implementations (and perhaps multiple interface base classes as well) to create a given concrete class.
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This topic is closed to new replies.

Advertisement