Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Versatile function with arguments of C++ iterators

Tags:

c++

I am using functions that accept a range by iterators, something similar to "printPoints" and "printPoints2" in following code. So far, "printPoints" can accept iterators of vector/list/etc of Point objects, but "printPoints2" is needed to handle vector/list/etc of POINTERs to Point objects. Is there any trick to write a more versatile function to replace those two?

Thanks in advance.

#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <memory>

struct Point {
    int x;
    int y;

    Point(int x, int y): x(x), y(y) {}
};

/*
 Is there a more versatile function to replace the following two?
 */
template <class Iter>
void printPoints(Iter begin, Iter end) {
    for(auto it=begin; it!=end; ++it)
        std::cout << "{" << it->x << " " << it->y << "}";
}
template <class Iter>
void printPoints2(Iter begin, Iter end) {
    for(auto it=begin; it!=end; ++it)
        std::cout << "{" << (*it)->x << " " << (*it)->y << "}";
}

int main()
{
    std::vector<Point> vecPoints = {{0,0}, {1,1}};
    std::cout << "vector of points: ";
    printPoints(vecPoints.begin(), vecPoints.end());
    std::cout << "\n";

    std::list<Point> listPoints = {{2,2}, {3,3}};
    std::cout << "list of points: ";
    printPoints(listPoints.begin(), listPoints.end());
    std::cout << "\n";

    std::vector<std::unique_ptr<Point>> vecPtrPoints;
    vecPtrPoints.push_back(std::make_unique<Point>(4,4));
    vecPtrPoints.push_back(std::make_unique<Point>(5,5));
    std::cout << "vector of pointers to point: ";

    // won't work because of "it->x" inside the function
    //printPoints(vecPtrPoints.begin(), vecPtrPoints.end());
    printPoints2(vecPtrPoints.begin(), vecPtrPoints.end());
    std::cout << "\n";
}
like image 982
sofname Avatar asked Mar 16 '26 03:03

sofname


1 Answers

C++17 to the rescue!

#include <type_traits>

template <class Iter>
void printPoints(Iter begin, Iter end) {
    for(auto it=begin; it!=end; ++it)
    {
        if constexpr (std::is_same_v<typename std::iterator_traits<Iter>::value_type, Point>)
        {
            std::cout << "{" << it->x << " " << it->y << "}";
        }
        else
        {
            std::cout << "{" << (*it)->x << " " << (*it)->y << "}";
        }
    }
}

If you don't have c++17 then you can achieve something similar by using std::enable_if to allow you to have both of your printPoints functions to with the same name.

Another approach would be to refactor your code:

void printPoint(const Point& point)
{
    std::cout << "{" << point.x << " " << point.y << "}";
}

void printPoint(const std::unique_ptr<Point>& point)
{
    printPoint(*point);
}

template <class Iter>
void printPoints(Iter begin, Iter end) {
    for(auto it=begin; it!=end; ++it)
    {
        printPoint(*it);
    }
}

This is a little more verbose but will work in earlier c++ standards and might be easier to understand for novice c++ programmers.

Option 3 is a combination of both:

void printPoint(const Point& point)
{
    std::cout << "{" << point.x << " " << point.y << "}";
}

template <class Iter>
void printPoints(Iter begin, Iter end) {
    for(auto it=begin; it!=end; ++it)
    {
        if constexpr (std::is_same_v<typename std::iterator_traits<Iter>::value_type, Point>)
        {
            printPoint(*it);
        }
        else
        {
            printPoint(**it);
        }
    }
}
like image 65
Alan Birtles Avatar answered Mar 17 '26 17:03

Alan Birtles