Avoid function overloading with help of template - c++

I have the following overloaded functions:
float myFunc(Vector2D vec) {
Temp2D temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
float myFunc(Vector3D vec) {
Temp3D temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
float myFunc(Vector4D vec) {
Temp4D temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
where computeTemp is also overloaded for Vector2D, Vector3D, Vector4D:
Temp2D computeTemp(Vector2D, int);
Temp3D computeTemp(Vector3D, int);
Temp4D computeTemp(Vector4D, int);
To avoid code duplication, I have come up with the idea to add a layer of abstraction:
template<typename T0, typename T1>
float myFunc(T0 vec) {
T1 temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
float myFunc(Vector2D vec) {
return myFunc<Vector2D, Temp2D>(vec);
}
float myFunc(Vector3D vec) {
return myFunc<Vector3D, Temp3D>(vec);
}
float myFunc(Vector4D vec) {
return myFunc<Vector4D, Temp4D>(vec);
}
However, I want to know if it is possible to avoid an additional layer of abstraction and directly decide the type of variable temp in myFunc.

directly decide the type of variable temp in myFunc.
You can use decltype to determine the type, e.g.
template<typename T0>
float myFunc(T0 vec) {
decltype(computeTemp(vec, 0)) temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
BTW,
1. Note that if computeTemp returns by reference instead of return-by-value, the result type of decltype would also be reference (to lvalue or rvalue, depending on how computeTemp returns); you might need to use std::remove_reference with decltype to get the type you want.
2. The expression used for decltype belongs to unevaluated expressions:
The operands of the four operators typeid, sizeof, noexcept, and decltype (since C++11) are expressions that are not evaluated (unless they are polymorphic glvalues and are the operands of typeid), since these operators only query the compile-time properties of their operands. Thus, std::size_t n = sizeof(std::cout << 42); does not perform console output.

Or using auto instead of decltype()
template <typename VT>
float myFunc(VT vec) {
auto temp = computeTemp(vec, 0);
for (int i = 1; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}
This starting from C++11 (same limit for decltype()).
For C++98, the best I can imagine is the creation of a custom type traits to select the temp type.
Something like [caution: code not tested]
template <typename>
struct tempType;
template <> struct tempType<Vector2D> { typedef Temp2D type; };
template <> struct tempType<Vector3D> { typedef Temp3D type; };
template <> struct tempType<Vector4D> { typedef Temp4D type; };
template <typename VT>
float myFunc(VT vec) {
typename tempType<VT>::type temp;
for (int i = 0; i < 10; i++) {
temp += computeTemp(vec, i);
}
return temp.compute_a_float();
}

Related

C++: function that works with container and container of pointers as well

I think I'm facing something that I imagine is a quite common problem here.
I'd like to write a function that would be able to accept both a container (let's say std::vector) of objects, and a container of pointers to those objects.
What would be the proper way to do so?
Right now, I'm thinking
int sum(std::vector<int *> v)
{
int s = 0;
for (int * i : v) s += *i;
return s;
}
int sum(std::vector<int> v)
{
std::vector<int *> vp;
for (size_t i = 0; i < v.size(); ++i)
vp[i] = &v[i];
return sum(vp);
}
But it doesn't seem quite right, does it?
Consider the standard algorithm library where the problem you see has a solution.
Most algorithms have some default behavior but often allow you to customize that behavior via functor parameters.
For your specific case the algorithm of choice is std::accumulate.
Because this algorithm already exists I can restrict to a rather simplified illustration here:
#include <iostream>
#include <functional>
template <typename T,typename R,typename F = std::plus<>>
R sum(const std::vector<T>& v,R init,F f = std::plus<>{})
{
for (auto& e : v) init = f(init,e);
return init;
}
int main() {
std::vector<int> x{1,2,3,4};
std::vector<int*> y;
for (auto& e : x ) y.push_back(&e);
std::cout << sum(x,0) << "\n";
std::cout << sum(y,0,[](auto a, auto b) {return a + *b;});
}
std::plus is a functor that adds two values. Because the return type may differ from the vectors element type an additional template parameter R is used. Similar to std::accumulate this is deduced from the initial value passed as parameter. When adding int the default std::plus<> is fine. When adding integers pointed to by pointers, the functor can add the accumulator with the dereferenced vector element. As already mentioned this is just a simple toy example. In the above link you can find a possible implementation of std::accumulate (which uses iterators rather than the container directly).
With C++20 (or another ranges library), you can easily add or remove pointerness
template <std::ranges::range R, typename T>
concept range_of = requires std::same<std::ranges::range_value_t<R>, T>;
template <range_of<int *> IntPointers>
int sum_pointers(IntPointers int_pointers)
{
int result = 0;
for (int * p : int_pointers) result += *p;
return result;
}
void call_adding_pointer()
{
std::vector<int> v;
sum_pointers(v | std::ranges::views::transform([](int & i){ return &i; });
}
Or
template <range_of<int> Ints>
int sum(Ints ints)
{
int result = 0;
for (int i : ints) result += i;
return result;
}
void call_removing_pointer()
{
std::vector<int *> v;
sum(v | std::ranges::views::transform([](int * p){ return *p; });
}
You can make a function template, which behaves differently for pointer and non-pointer:
#include <iostream>
#include <vector>
using namespace std;
template <class T>
auto sum(const std::vector<T> &vec)
{
if constexpr (std::is_pointer_v<T>)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec) sum += *value;
return sum;
}
if constexpr (!std::is_pointer_v<T>)
{
T sum = 0;
for (const auto & value : vec) sum += value;
return sum;
}
}
int main(){
std::vector<int> a{3, 4, 5, 8, 10};
std::vector<int*> b{&a[0], &a[1], &a[2], &a[3], &a[4]};
cout << sum(a) << endl;
cout << sum(b) << endl;
}
https://godbolt.org/z/sch3KovaK
You can move almost everything out of the if constexpr to reduce code duplication:
template <class T>
auto sum(const std::vector<T> &vec)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec)
{
if constexpr (std::is_pointer_v<T>)
sum += *value;
if constexpr (!std::is_pointer_v<T>)
sum += value;
}
return sum;
}
https://godbolt.org/z/rvqK89sEK
Based on #mch solution:
template<typename T>
std::array<double, 3> center(const std::vector<T> & particles)
{
if (particles.empty())
return {0, 0, 0};
std::array<double, 3> cumsum = {0, 0, 0};
if constexpr (std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] += p->getX();
cumsum[1] += p->getY();
cumsum[2] += p->getZ();
}
}
if constexpr (not std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] += p.getX();
cumsum[1] += p.getY();
cumsum[2] += p.getZ();
}
}
double f = 1.0 / particles.size();
cumsum[0] *= f;
cumsum[1] *= f;
cumsum[2] *= f;
return cumsum;
}
Much cleaner and more efficient solution using std::invoke:
std::array<double, 3> centroid(const std::vector<T> & particles)
{
if (particles.empty())
return {0, 0, 0};
std::array<double, 3> cumsum{0.0, 0.0, 0.0};
for (auto && p : particles)
{
cumsum[0] += std::invoke(&topology::Particle::getX, p);
cumsum[1] += std::invoke(&topology::Particle::getY, p);
cumsum[2] += std::invoke(&topology::Particle::getZ, p);
}
double f = 1.0 / particles.size();
cumsum[0] *= f;
cumsum[1] *= f;
cumsum[2] *= f;
return cumsum;
}

template-determined class as return-type of a template function, that uses template template parameters

I have std::vector<double> , and my type Array-like.
Also, there are a lot of numbers in std::vector<double> source, and I would like to make this code work in the way:
std::cout << toHisto(source, /* other parameters like min, max, stacks */);
And I guessed that would be good, if the function toHisto worked with other containers, such as with my Array. Thou, I tried to do this, and have a problem in return type of template function Error C3200 'Container<double,std::allocator<double>>': invalid template argument for template parameter 'Container', expected a class template
If you have any proposals, I would be glad to hear.
Code here:
template<template <class T, class Allocator = std::allocator<T>> class Container>
class MyClassForOstream {
public:
Container<double> &res;
double min;
double max;
MyClassForOstream(Container<double> r, double min_, double max_) : res(r), min(min_), max(max_) {}
friend std::ostream& operator<< (std::ostream& out, MyClassForOstream const& s) {
/* sth like for(i in s)"out << s[i]" , and some format stuff from <iomanip>*/
return out;
}
};
template<template <class T, class Allocator = std::allocator<T>> class Container>
// Error here at specifing params for MyClassForOstream < /* here */>
MyClassForOstream<Container<double>> toHisto(Container<double> const& gen, double min, double max, size_t stocks) { // class template must have: operator[], .size(), ctor(size)
Container<size_t> tmp(stocks);
for (size_t i = 0; i != stocks; ++i) {
tmp[i] = 0;
}
for (size_t i = 0; i != gen.size(); ++i) {
++tmp[static_cast<size_t>(((min + gen[i]/(max+min))*((max-min)*stocks)))];
}
Container<double> res(stocks);
for (size_t i = 0; i != res.size(); ++i) {
res[i] = 0;
}
for (size_t i = 0; i != res.size(); ++i) {
res[i] = static_cast<double>(tmp[i])/gen.size();
}
return MyClassForOstream<Container<double>> (res, min, max ); // also error here
}```

Make type of vector arbitrary

I'm currently implementing a simple stack in c++17, which has to have a toString-Method, that can deal among other types with std::vector as the template type.
My original problem was, that std::to_string can't accept vector's to I added a specialized implementation for vector's on top of my normal implementation:
template <class T> std::string SweCpp::Stack<T>::toString()
{
std::string result = "SweCpp::Stack[";
for (int a = 0; a < top + 1; a++)
{
T element = st[a];
result += std::to_string(element);
if (a != top) {
result += ", ";
}
}
result += "]";
return result;
}
template <> std::string SweCpp::Stack<std::vector<double>>::toString() {
std::string result = "SweCpp::Stack[";
for (int a = 0; a < top + 1; a++)
{
std::vector<double> element = st[a];
std::string vecAsString = "Vector{";
for (double value : element) {
vecAsString += std::to_string(value) + ",";
}
vecAsString += "}";
result += vecAsString;
if (a != top) {
result += ", ";
}
}
result += "]";
return result;
}
The idea is to call to_string for every element of the vectors, and build a string like that. The problem is, that I can't figure out, how to make the double arbitrary. How can I implement it, so that I can pass any type of vector, e.g. std::vector<int> and std::vector<std::string>?
Something along these lines, perhaps:
template <typename T>
std::string MyToString(const T& val) {
return std::to_string(val);
}
template <typename T>
std::string MyToString(const std::vector<T>& vec) {
std::string vecAsString = "Vector{";
for (const auto& value : vec) {
vecAsString += MyToString(value) + ",";
}
vecAsString += "}";
return vecAsString;
}
Now implement Stack<T>::toString() as in your first example, but call MyToString in place of std::to_string

Can't find error in template class

template< typename T >
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0
for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
tSum += tArray[nIndex];
}
// convert T to double
return double(tSum) / nElements;
};
template <typename T>
class pair {
public:
T a;
T b;
pair () {
a=T(0);
b=T(0);
} ;
pair (T a1, T b1) {
a=a1;
b=b1;
};
pair operator += (pair other_pair) {
return pair(a+other_pair.a, b+other_pair.b);
}
operator double() {
return double(a)+ double(b);
}
};
int main(void)
{
pair<int > p1[1];
p1[0]=pair<int >(3,4);
std::cout<< GetAverage <pair <int >>(p1,1) <<"\n";
}
I can't understand why it prints 0 instead of 3.5.
When I copy code from C++ -- How to overload operator+=? all went fine. But I can't understand where I have made
a mistake
pair operator += (pair other_pair) {
return pair(a+other_pair.a, b+other_pair.b);
}
should be
pair &operator += (const pair &other_pair) {
a += other_pair.a;
b += other_pair.b;
return *this;
}
You need to modify the members of this and return a reference to *this, instead of a new object.
It is also a good idea to pass other_pair as a const reference instead of by value.

Function template for string and int in C++

I want to have a function template which takes a vector and an element and returns the position of this element in the vector. I want this function to be applicable for both int and std::string types. This is the function template definition:
template<class T>
int findElement(const vector<T> &vec, const T &ele)
{
for(size_t i = 0; i < vec.size(); i++)
{
if(typeid(ele) == typeid(std::string))
{
if(ele.compare(vec[i]) == 0)
return i;
}
else
{
if(ele == vec[i])
return i;
}
}
return -1;
}
As you can see, I am checking the types initially so that I can use the appropriate comparison method. This works fine when I call with std::string type parameters but it gives the following error when I use it with double type:
error C2228: left of '.compare' must have class/struct/union
and
see reference to function template instantiation 'int findElement<double>(const std::vector<_Ty> &,const T &)' being compiled
How do I solve this issue?
Thanks,
Rakesh.
You should never have to check typeid when using templates. std::string defines == in the expected manner, so use it!
template<class T>
int findElement(const vector<T> &vec, const T &ele)
{
for(size_t i = 0; i < vec.size(); i++)
{
if(ele == vec[i])
return i;
}
return -1;
}
In general, if you need to special-case your templated function for a particular type, use a template specialization:
template<class T>
int findElement(const vector<T> &vec, const T &ele) {
for(size_t i = 0; i < vec.size(); i++) {
if(ele == vec[i])
return i;
return -1;
}
template<>
int findElement<std::string>(const vector<std::string> &vec, const std::string &ele) {
for(size_t i = 0; i < vec.size(); i++) {
if(ele.compare(vec[i]) == 0)
return i;
}
return -1;
}
std::string has operator ==, however, if you want call different methods from T - you should specialize, or overload function. Typeid can't help, since it's runtime type identification.
Example of overloading
template<class T>
int findElement(const vector<T> &vec, const T &ele)
{
for(size_t i = 0; i < vec.size(); i++)
{
if(ele == vec[i])
return i;
}
return -1;
}
int findElement(const vector<string>& vec, const string& ele)
{
for (size_t i = 0; i < vec.size(); ++i)
{
if (ele.compare(vec[i]) == 0)
return i;
}
return -1;
}
also, you can use function overloading only for compare, since loop is the same.