Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unify C++ templates for pointers, values and smart pointers

My real example is quite big, so I will use a simplified one. Suppose I have a data-type for a rectangle:

struct Rectangle {
  int width;
  int height;

  int computeArea() {
    return width * height;
  }
}

And another type that consumes that type, for example:

struct TwoRectangles {
  Rectangle a;
  Rectangle b;
  int computeArea() {
    // Ignore case where they overlap for the sake of argument!
    return a.computeArea() + b.computeArea();
  }
};

Now, I don't want to put ownership constraints on users of TwoRectangles, so I would like to make it a template:

template<typename T>
struct TwoRectangles {
  T a;
  T b;
  int computeArea() {
    // Ignore case where they overlap for the sake of argument! 
    return a.computeArea() + b.computeArea();
  }
};

Usages:

TwoRectangles<Rectangle> x;
TwoRectangles<Rectangle*> y;
TwoRectangles<std::shared_ptr<Rectangle>> z;
// etc... 

The problem is that if the caller wants to use pointers, the body of the function should be different:

template<typename T>
struct TwoRectangles {
  T a;
  T b;
  int computeArea() {
    assert(a && b);
    return a->computeArea() + b->computeArea();
  }
};

What is the best way of unifying my templated function so that the maxiumum amount of code is reused for pointers, values and smart pointers?

like image 501
sdgfsdh Avatar asked Jan 31 '17 15:01

sdgfsdh


1 Answers

One way of doing this, encapsulating everything within TwoRectangles, would be something like:

template<typename T>
struct TwoRectangles {
  T a;
  T b;

  int computeArea() {
    return areaOf(a) + areaOf(b);
  }

private:
    template <class U>
    auto areaOf(U& v) -> decltype(v->computeArea()) {
        return v->computeArea();
    }

    template <class U>
    auto areaOf(U& v) -> decltype(v.computeArea()) {
        return v.computeArea();
    }
};

It's unlikely you'll have a type for which both of those expressions are valid. But you can always add additional disambiguation with a second argument to areaOf().


Another way, would be to take advantage of the fact that there already is a way in the standard library of invoking a function on whatever: std::invoke(). You just need to know the underlying type:

template <class T, class = void>
struct element_type {
    using type = T;
};

template <class T>
struct element_type<T, void_t<typename std::pointer_traits<T>::element_type>> {
    using type = typename std::pointer_traits<T>::element_type;
};

template <class T>
using element_type_t = typename element_type<T>::type;

and

template<typename T>
struct TwoRectangles {
  T a;
  T b;

  int computeArea() {
    using U = element_type_t<T>;
    return std::invoke(&U::computeArea, a) + 
        std::invoke(&U::computeArea, b);
  }
};
like image 79
Barry Avatar answered Sep 21 '22 06:09

Barry