Reputation: 543
I have a question about what is proper/correct way to use templates in C++.
Let's say we have a template class like this:
template <typename T> class Foo
{
public:
T add(T n1, T n2);
};
template <typename T> T Foo<T>::add(T n1, T n2)
{
return(n1 + n2);
}
This will work very good with basic datatypes, like int, double, float, char ...etc.
Foo <int> foo_int;
std::cout << std::to_string(foo_int.add(2, 5)) << std::endl;
Foo <double> foo_double;
std::cout << std::to_string(foo_double.add(2.2, 6.1)) << std::endl;
Foo <char> foo_char;
std::cout << std::to_string(foo_char.add('A', 'm')) << std::endl;
But this will not work very well with complex datatypes, like this one:
class Bar
{
public:
std::string bar;
};
Bar bar_1;
Bar bar_2;
Foo <Bar> foo_bar;
std::cout << std::to_string(foo_int.add(bar_1, bar_2)) << std::endl;
Is it OK to write templates that only works with a handful datatypes, or should templates only be used in case where it works with all kind of datatypes, like std::vector.
In case, if it's proper to write templates that only works for a handful of datatypes, how should it be written? In case of class Foo
, I will know that Foo
only works with basic datatypes, but let say I give the code of Foo
to Alice, she doesn't know that. So she will get a compiler error if she uses Foo
with complex datatypes.
Is there away to tell compiler/prorammer that the templates only works with certain datatypes, except the errors the compiler will throw.
Upvotes: 4
Views: 113
Reputation: 63124
It's perfectly fine to write templates that work only with types that support certain operations. In fact, the opposite would make writing any template a feat of heroism.
The first error guard is documentation. Standard algorithms from <algorithm>
, for example, all receive iterators of arbitrary types. You know that they should be iterators because they're named and documented that way. Being an iterator implies that an instance must be able to be incremented, compared and dereferenced at the very least (but some algorithms require more). This set of abilities is known as the Iterator
concept, but that's only documentation still.
Enforcing a concept is already done implicitly when the compiler instantiates the template and tries to compile all of the operations you're performing on the user-provided type. However, you may want to report errors earlier and more clearly than with a ten-levels-deep error stack pointing to the innards of your own code. This can already be done in two major ways:
Hard errors, by means of static_assert
. You write a compile-time check for your concept, check it via static_assert
, and halt compilation if it fails.
SFINAE, where you design your code to graciously step out of the way if it cannot work with the user's object. This is very useful for functions, as it leaves rooms for other overloads if your generic version is not applicable.
These two rely on relatively complex metaprogramming techniques, but are well-known among C++ library writers.
Finally, we hope to get concepts as an actual language feature in C++20. You will then be able to write the above checks and error reporting with a natural syntax.
But all of this is just to handle failure cases well. No one is expected to write templates which work with every type under the sun.
Upvotes: 3
Reputation: 93264
But this will not work very well with complex datatypes, like this one:
That's because Bar
does not provide Bar::operator+(Bar, Bar)
- if it did, it would work with your template.
Is it OK to write templates that only works with a handful datatypes
Depending on your use case, yes. You usually want to support all datatypes that conform to a particular concept, but sometimes you might also want to support a fixed set of types.
Is there away to tell compiler/prorammer that the templates only works with certain datatypes, except the errors the compiler will throw.
If you want your template to work with all types that conform to a particular interface, you want to use concepts (available in C++20, can be emulated in C++11).
You can check properties about types with the detection idiom and use static_assert
(or SFINAE) to provide compile-time errors for users of your class.
Upvotes: 4