Template class as parameter question
-
I believe I understand template classes. I believe I know that when you write
QList<int> foo
you really get a "new class created at compile time" for this (or something like that, I don't think it matters here).I have a question about (trying) to use a template class "as is", without caring about what its
T
type is. Maybe asQList
itself does. But I want to use it for a parameter type to an (unrelated) function, I am not trying to derive from it or have it as its own class.Let's say I want to write a "helper" function somewhere which takes a
QList
parameter of any type and (for example) just wants to return the index for a random element. So I want to write something like:int randomIndex(QList list) { return randomNumberUpTo(list.count()); }
Note that this function makes no reference to the
T
type of a template/genericQList<T>
, and as far as I can see should not care about it (as perQList
itself). How can I write this (both in the.h
and the.cpp
if they differ)?Once I have the answer to that. Let's say I change my mind and want it to return a random element (or reference to such). Now I want something like:
T randomElement(QList<T> list) { int num = randomNumberUpTo(list.count()); return list.at(num); }
How do you write this one? I realise (I believe) this one will need to know what
T
to generate code, so it may be rather different from the first case. In fact I am hoping the first one can be done with knowing whatT
is where this one presumably cannot do so.Please remember these are only "helper" functions somewhere. They are to work on any
QList
parameter.. We are not talking about deriving a class fromQList
and writing code in that subclass. -
I'm sorry I don't get what exactly your problem now is ?
your:
T randomElement(QList<T> list) { int num = randomNumberUpTo(list.count()); return list.at(num); }
is pretty spot on: only the template type missing:
template <typename T> int randomIndex(const QList<T>& list) { return randomNumberUpTo(list.count()); }
and
template <typename T> const T & randomIndex(const QList<T>& list) { T num = randomNumberUpTo(list.count()); return list.at(num); }
There is no cpp file case, templates are headers only :D
You don't need to know what type T is, you can query it to make sure you're actually dealing with an expected type for example:
#include <type_traits> // für std::is_arithmetic template <typename T> int randomIndex(const QList<T>& list) { static_assert(std::is_arithmetic<T>::value, "randomIndex requires a numeric type in QList"); return randomNumberUpTo(list.count()); }
But you don't have to. You should, but we should do many things and don't :P
-
@J-Hilk
Hi, thanks. Quite agree your examples work, and are just what was wanted.I am learning (a bit more!) about templates. I cannot recall exactly what I actually had when I typed in my sample code here. It might (well) be that at that time I was trying to put the body/definition of the template functions/methods inside the
.cpp
. I prefer all my code blocks in.cpp
s instead of.h
s. I have now read how we cannot do that for templates (unless a complex hack of some intermediate extra.cpp
file, which we won't go into).So it was straightforward at least in
.h
file, and I mark your answer as solution, thanks. -
-
@JonB said in Template class as parameter question:
I prefer all my code blocks in
.cpp
s instead of.h
s.I have seen people use
.tpp
s for template implementations. Those could be included inside the correspoding.h
s. If you know all the possible types for a template at compile time, you can mark them asextern
in the header file and use explicit instantiation in the .cpp file.Again (as I answered in a different post), modules come to the rescue. I haven't played with modules yet, but I believe you can split templates inside modules into separate files.
Back to your original problem, modern C++ has a lot of features I haven't used. In your concrete case "concepts" come to mind. You could have a QListConcept and then write
int randomIndex(auto QListConcept &list);
. The STL has some concepts for their different kind of containers, but I am not aware of concepts specifically for Qt. You can also not write a concept for QList, but instead use arequires
clause for your function. ForrandomIndex()
the only requirement is to be able to call a member methodcount()
on the object passed into the function. (I am not entirely sure about the syntax, but it is something along the lines oftemplate<class T> requires(T t) { t.count(); } int randomIndex(const T &list);
.) Written this way, you could pass in any other container that has this method. Even better if you are usingsize()
instead ofcount()
(both are available in Qt) because then you could even use this function with STL containers besides just Qt containers. That being said: I am against writing general purpose code from the get go. Even though it is nice if you could use it for many different use cases, it makes it a little more complicated to read (and write bugfree and debug). It is thus unnecessary if you are using this function only with QList for the next decade. If the case arises that you want to reuse this function, only then you should think about refactoring it to use more complex concepts. -
@SimonSchroeder said in Template class as parameter question:
If you know all the possible types for a template at compile time, you can mark them as extern in the header file and use explicit instantiation in the .cpp file.
Yeah, I came across that one and that's why I wrote earlier " (unless a complex hack of some intermediate extra .cpp file, which we won't go into)."!
"concepts" come to mind.
And wtf are these? (Yes I could Google!)
template<class T> requires(T t) { t.count(); }
requires(T t)
OMG! I am only just starting ontemplate
without extra stuff!Written this way, you could pass in any other container that has this method.
What happens when another container has a different definition of
count()
which does not return anint
(or number)? Maybe your syntax is not quite correct and it would have to berequires(T t) { int t.count(); }
?I am against writing general purpose code from the get go
Hmph! But that's is just what e.g.
QList
is, and loads of other containers. Maybe you will argue different semantics, buttemplate
is a huge part of modern C++ in practice and I would call templates an example of "general purpose code".My personal overall observation/usage. I loved the simplicity/clarity of C. I totally accept the need to use C++ now and (despite what you may think!) feel I am comfortable with it. However through choice I prefer to use the more "straightforward" stuff in it. I am especially bugged by the ever growing complexity of the syntax as more and more new "features" are added with each release. It just gets so complicated to read, never mind write. So I don't go using new/additional stuff till I find a need to.
-
@JonB said in Template class as parameter question:
What happens when another container has a different definition of count() which does not return an int (or number)? Maybe your syntax is not quite correct and it would have to be requires(T t) { int t.count(); }?
I guess this would be some additional requirement using
std::is_same_v
anddecltype(t.count())
. I'm still on C++17, so I haven't used concepts myself.@JonB said in Template class as parameter question:
Hmph! But that's is just what e.g. QList is, and loads of other containers.
A container class is an obvious example where a general purpose implementation makes sense. But, you usually don't implement your own containers. Furthermore, most of the time I would have at least
int
anddouble
in mind for containers. So, making them general purpose from the get go is a given. But, I don't have to make all my algorithms general purpose such that they might be reusable decades down the line if ever. If there is a current need for general purpose code, make it general purpose. Otherwise it just make maintenance more complicated while being unnecessary. I guess it might also introduce additional bugs because you try to be extra clever.