Is it possible to create a template that accepts type T which is an object or pointer to an object?

Hi guys,

So I am wondering is it possible to create a template that will handle two different types simultanoulsy.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Point
{
public:
  Point(int x, int y) : x(x), y(y) {};
  int x;
  int y;
};

template<typename T>
Point AddPoints(const T& A, const T& B)
{
  Point result(A.x + B.x, A.y + B.y);
  return result;
}

int main()
{
  Point A(1,1);
  Point B(2,2);
  Point C = AddPoints(A, B);
}


The above code will work fine for Point type, but if I would pass instead unique_ptr<Point> this will not work because I would have to access x and y values via -> operator. Is it possible to handle these two types inside of a template?

The template above is just an example, in real code there is a bit more of math involved.

Thanks for any hints.
Dereference the pointers and pass the objects to the function.

1
2
3
std::unique_ptr<Point> A = std::make_unique<Point>(1,1);
std::unique_ptr<Point> B = std::make_unique<Point>(2,2);
Point C = AddPoints(*A, *B);


Thanks but, that was not the question.
You may use std::enable_if:

1
2
3
4
5
  template<typename TType>
  typename std::enable_if<std::is_floating_point<TType>::value, bool>::type FromStr(TType *const val, const std::string &str)
{
  ...
}

See:

http://www.cplusplus.com/reference/type_traits/enable_if/?kw=enable_if
http://www.cplusplus.com/reference/type_traits/
How does this allow me to handle two different types?
As far as I understood it correctly, this will prevent template from instantiation if given type T has not specific traits.
I'm not convinced that treating objects and pointers to objects interchangeably is a good idea.
Nevertheless, if we must, something like this can be done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <type_traits>

struct point { int x = 0 ; int y = 0 ; };

template < typename T > decltype(auto) to_pt( const T& arg )
{
   if constexpr ( std::is_convertible_v<T,point> ) return arg ;
   else return point{ arg->x, arg->y } ; // otherwise, expect pointer or smart pointer
}

template < typename T, typename U > point plus_xy( const T& a, const U& b )
{ return { to_pt(a).x + to_pt(b).x, to_pt(a).y + to_pt(b).y } ; }

int main()
{
    const point a { 1, 12 } ;
    struct point2 : point {};
    const point2 b { { 3, 45 } } ;

    struct smart_ptr { const point& p ; const point* operator->() const { return std::addressof(p) ; } };
    const smart_ptr pa{a} ;
    const smart_ptr pb{b} ;

    for( auto[x,y] : { plus_xy(a,b), plus_xy(a,&b), plus_xy(a,pb), plus_xy(&a,pb), plus_xy(pa,pb) } )
        std::cout << x << ", " << y << '\n' ;
}

http://coliru.stacked-crooked.com/a/5a7f0077543cbabf
@JLBorges

Thank you for your time and the example, if you don't mind I have a couple of questions:

1.
"I'm not convinced that treating objects and pointers to objects interchangeably is a good idea." Why? Isn't this the whole point of templates? To use shared properties of different types?

2. Why did you come up with such complicated example? Here is what I did based on your example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <type_traits>
#include <memory>

struct point { int x = 0 ; int y = 0 ; };

template < typename T > decltype(auto) to_pt( const T& arg )
{
   if constexpr ( std::is_convertible_v<T,point> ) return arg ;
   else return *arg; // otherwise, expect pointer or smart pointer
}

template < typename T, typename U > point plus_xy( const T& a, const U& b )
{ return { to_pt(a).x + to_pt(b).x, to_pt(a).y + to_pt(b).y } ; }

int main()
{
    const point a { 1, 12 } ;
    struct point2 : point {};
    const point2 b { { 3, 45 } } ;
    std::unique_ptr<point> c = std::make_unique<point>();
    c->x = 7;
    c->y = 10;

    for( auto[x,y] : { plus_xy(a,b), plus_xy(b,c) } )
        std::cout << x << ", " << y << '\n' ;

}


Your original code was constructing a copy of point object:

1
2
3
4
5
template < typename T > decltype(auto) to_pt( const T& arg )
{
   if constexpr ( std::is_convertible_v<T,point> ) return arg ;
   else return point{ arg->x, arg->y } ; // otherwise, expect pointer or smart pointer
}


My version is simply dereferencing a pointer. Does this mean that mine version does not constructing a new object? It probably still does... so, can this be further improved to return a reference to *T so there will be no new copies of passed object?

3.
Where can I read about
for( auto[x,y] : { } )

I have never seen auto[x,y] before.

Thank you!
> Why? Isn't this the whole point of templates? To use shared properties of different types?

My view is that treating a pointer to T and an object of type T interchangeably is somewhat unintuitive; this is not the way that most programmers think about pointers.


> Your original code was constructing a copy of point object:
> My version is simply dereferencing a pointer.

Your version too would create a new value if the type T is some type foo which provides an implicit conversion to point eg. struct foo { operator point() const ; /* ... */ };

point is a trivial type with a very small footprint; there is very little overhead involved in creating a copy.


> Where can I read about for( auto[x,y] : { } )

Structured binding: https://en.cppreference.com/w/cpp/language/structured_binding
Thank you for explanation, that was very helpful.
Topic archived. No new replies allowed.