Tarvoc
Tarvoc

Reputation: 83

C++ function with different return types

Let's say I want to write a function that takes a plane and a line in R^3 and returns their intersection. Obviously, I have to distinguish between three possible cases: i) the line intersects the plane in a single point, ii) the line is a subset of the plane, iii) the line is parallel to the plane (and not a subset of it). This results in three possible return types for the function.

I've been working a lot with OCaml recently, which would allow me to distinguish between these different types very explicitly by returning a variant type from my function. How do people deal with this kind of issue in C++?

One idea that comes to mind is to use a tuple of {bool, bool, vector} as my return type, where the first boolean says whether the line and the plane have a non-empty intersection, the second boolean says whether they intersect in a single point if the first boolean is true (and is meaningless otherwise), and the vector returns the unique intersection if both booleans are true (and is meaningless otherwise). However, this feels very inelegant and hacky, I have to inform users of the function of the meaning of the tuple entries using comments, I return variables which can be meaningless, etc.

What is the best way to deal with this problem?

Upvotes: 2

Views: 1049

Answers (4)

n. m. could be an AI
n. m. could be an AI

Reputation: 119877

Here are several generic (i.e. not limited to geometrical lines and points) ways to cope with the problem.

  1. std::variant (or its older sibling boost::variant for those who cannot run C++17).
  2. Plain old union (tagged):

    struct LinePlaneIntersection {
      enum { IsLine, IsPlane } intersection_type;
      union {
         Point p;
         Line l;
      };
    };
    

    If Point and Line have not-trivial constructors and/or destructors, you'd need to add ctors and dtors to the above scheme.

  3. Plain old inheritance.

    class LinePlaneIntersection { ... };
    class NoIntersection : public LinePlaneIntersection { ... };
    class OnePointIntersection : public LinePlaneIntersection { ... };
    class OneLineIntersection : public LinePlaneIntersection { ... };
    

    Return a LinePlaneIntersection* (or better and much preferable std::unique_ptr<LinePlaneIntersection>) from your function. Then there's of course the problem of what to do with the returned value. You may want to use the Visitor pattern here.

  4. Continuation passing. Don't return anything, accept a continuation instead. In this case, three continuations:

    void intersect (Line line, Plane plane,
                    std::function<void(Line)> onLine,
                    std::function<void(Point)> onPoint,
                    std::function<void()> onNothing);
    

Upvotes: 5

Jarod42
Jarod42

Reputation: 217275

I would do something like:

struct Point3D
{
    double x;
    double y;
    double z;
};

struct Line
{
    Point3D p1;
    Point3D p2;
};

struct Plan {
    Point3D p;
    Point3D orthogonalDir;
};

std::optional<std::variant<Point3D, Line>>
ComputeIntersection(const Line& line, const Plan& plan);

Upvotes: 4

Jonathan Olson
Jonathan Olson

Reputation: 1186

Why not return a struct with an enum type? Someone using the function could then first check the type of intersection before attempting to use the data.

enum IntersectType {
    INTERSECTION_NONE,
    INTERSECTION_POINT,
    INTERSECTION_LINE,
};

struct Point3D {
    double x;
    double y;
    double z;
}

struct LinePlaneIntersect {
    IntersectType type;
    std::vector<Point3D> intersect; //since you mentioned vector
};

//Check within another function
struct LinePlaneIntersect intersection = fun(line, plane);

if (intersection.type == INTERSECTION_POINT) {
    // do something
}

Upvotes: 1

Bathsheba
Bathsheba

Reputation: 234715

Although there are nice new shiny ways of dealing with this (std::tuple, std::variant, &c.), the tried-and-tested way is to design a class (or even a set of related classes) that can represent the various states and return an instance of that.

It's this approach that always seems to scale up best as your project evolves. So much so, that the committee behind Java has never emitted a tuple or a variant type into their language and libraries.

Upvotes: 1

Related Questions