2

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)

Lou
  • 4,244
  • 3
  • 33
  • 72
  • 5
    From C++20 you can use [concepts](https://en.cppreference.com/w/cpp/language/constraints), which seems like what you're looking for. – cigien Feb 07 '21 at 00:20
  • @cigien: sorry, fixed. Weird, so I am not mistaken then. It took a while for this to be added to the standard. :) But how do you deduce these signatures in practice? – Lou Feb 07 '21 at 00:25
  • I'm not sure what you mean. Most of the machinery for template argument deduction has been in the language for a long time. Are you asking how compilers implement this? Either way, you should edit the question accordingly. – cigien Feb 07 '21 at 00:29
  • @cigien: no, I am not asking about how the compiler deduces the arguments, what I am asking is, how does a programmer know how to implement a concrete instance of `T` which can be passed to `template `? For example, for `template T max(T a, T b) { return a > b ? a : b; }`, your `T` class must implement the `>` operator. You learn this from reading the entire function. So, a large templated class might contain dozens of these constraints, and coming from C# I expected that it would be easier to deduce them (for a programmer). – Lou Feb 07 '21 at 00:39
  • In other words, if you have a function `int doSomething(BaseClass b)`, and `BaseClass` is an abstract class, you only need to look at the `BaseClass` to know which methods you need to implement. You don't need to look at the body of the `doSomething` function to see this. – Lou Feb 07 '21 at 00:41
  • 1
    That's what Concepts gives you: you could say something like `template T max(T, T);` and not have to look at the definition. Again, please add all these clarifications into the question, not as comments. – cigien Feb 07 '21 at 00:42
  • 1
    If all you are doing is trying to get a lambda into a function, you can instead "wrap" the lambda with a `std::function`, and pass that. If the functions you're trying to pass as parameters always have the same signature, this avoids having to use template nonsense to make it work. [Example of std::function](https://shaharmike.com/cpp/lambdas-and-functions/#simple-example). – Perette Feb 07 '21 at 01:48

1 Answers1

3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524