How to know the required interface/contract of template arguments in C++?

Viewed 71

Sorry for the newbie question, but I have a feeling I am missing something here:

If I have a certain class template which looks like this (basically the only way to pass a lambda to a function in C++, unless I am mistaken):

template<typename V, typename F>
class Something
{
public:
     int some_method(V val, F func) {
         double intermediate = val.do_something();
         return func(intermediate);
     }
}

By reading the implementation of this class, I can see that the V class must implement double do_something(), and that F must be a function/functor with the signature int F(double).

However, in languages like Java or C#, the constraints for the generic parameters are explicitly stated in the generic class signature, so they are obvious without having to look at the source code, e.g.

class Something<V> where V : IDoesSomething // interface with the DoSomething() method
{
     // func delegate signature is explicit
     public int SomeMethod(V val, Func<double, int> func)  
     {
         double intermediate = val.DoSomething();
         return func(intermediate);
     }
}

My question is: how do I know how to implement more complex input arguments in practice? Can this somehow be documented using code only, when writing a library with template classes in C++, or is the only way to parse the code manually and look for parameter usage?

(or third possibility, add methods to the class until the compiler stops failing)

1 Answers

C# and Java Generics have similar syntax and some common uses with C++ templates, but they are very different beasts.

Here is a good overview.

In C++, by default template checking was done by instantiation of code, and requrements are in documentation.

Note that much of the requirements of C++ templates is semantic not syntactic; iterators need not only have the proper operations, those operations need to have the proper meaning.

You can check syntactic properties of types in C++ templates. Off the top of my head, there are 6 basic ways.

  1. You can have a traits class requirement, like std::iterator_traits.

  2. You can do SFINAE, an accidentally Turing-complete template metaprogramming technique.

  3. You can use concepts and/or requires clauses if your compiler is modern enough.

  4. You can generate static_asserts to check properties

  5. You can use traits ADL functions, like begin.

  6. You can just duck type, and use it as if it had the properties you want. If it quacks like a duck, it is a duck.

All of these have pluses and minuses.

The downside to all of them is that they can be harder to set up than "this parameter must inherit from the type Foo". Concepts can handle that, only a bit more verbose than Java.


Java style type erasure can be dominated using C++ templates. std::function is an example of a duck typed type eraser that allows unrelated types to be stored as values; doing something as restricted as Java is rarely worthwhile, as the machinery to do it is harder than making something more powerful.

C# reification cannot be fully duplicated by C++, because C#'s runtime environment ships with a compiler, and can effectively compile a new type when you instantiate at runtime. I have seen people ship compilers with C++ programs, compile dynamic libraries, then load and execute them, but that isn't something I'd advise.


Using modern , you can:

template<Number N>
struct Polynomial;

where Number is a concept which checks N (a type) against its properties. This is a bit like the Java signature stuff on steroids.

And by you'll be able to use compile time reflection to do things that make templates look like preprocessor macros.

Related