2

I work a lot with point clouds, and so I use a lot the PCL and Eigen libraries. Points in PCL have a .x .y and .z public members. Points in Eigen have .x() .y() and .z()

I frequently find myself writing a function for one point type and then calling it by creating a temp point to convert from one type to the other:

e.g.

void f(const Eigen::Vector3d &p) { ... }
pcl::PointXYZ p;
f(Eigen::Vector3d(p.x, p.y, p.z);

To further complicate the problem, PCL points come in various types: XYZ, XYZRGB, XYZN, etc. (see http://pointclouds.org/documentation/tutorials/adding_custom_ptype.php) and Eigen vectors come in several types as well: Vector3d for doubles and Vecto3f for floats, both a specialization of the Matrix type (see http://eigen.tuxfamily.org/dox/group__matrixtypedefs.html)

I was wondering if there could be some template specialization magic incantation that could be invoked to avoid that, i.e. it would detect whether the point type has a .x() or a .x and use appropriately.

quepas
  • 956
  • 1
  • 13
  • 30
brice rebsamen
  • 664
  • 6
  • 11

2 Answers2

1

For detecting if a class has a method or member variable with certain name, you can use a classical SFINAE approach:

template<typename T>
class has_x {
    private:
        typedef char yes[1];
        typedef char no[2];

        template<typename U> static yes& test_member(decltype(U::x));
        template<typename U> static no& test_member(...);

        template<typename U, U> struct check;
        template<typename U> static yes& test_method(check<float (U::*)(), &U::x>*);
        template<typename U> static no& test_method(...);

    public:
        static const bool as_method = (sizeof(test_method<T>(0)) == sizeof(yes));
        static const bool as_member = (sizeof(test_member<T>(0)) == sizeof(yes));
};

Which can be tested in a tiny test suite:

struct something_else {
};

struct fn_vec {
    float x() const { return 66; };
};

struct mem_vec {
    mem_vec() : x(55) {};
    float x;
};

int main(int argc, char*argv[]) {
    std::cout << "fv: " << has_x<fn_vec>::as_method << std::endl;
    std::cout << "mv: " << has_x<mem_vec>::as_method << std::endl;
    std::cout << "se: " << has_x<something_else>::as_method << std::endl;

    std::cout << std::endl;

    std::cout << "fv: " << has_x<fn_vec>::as_member << std::endl;
    std::cout << "mv: " << has_x<mem_vec>::as_member << std::endl;
    std::cout << "se: " << has_x<something_else>::as_member << std::endl;

    return 0;
}

which outputs:

fv: 1
mv: 0
se: 0

fv: 0
mv: 1
se: 0

You can then use it with std::enable_if to write variants of functions or structures specific to each of the types. You can also use logical operators in std::enable_if's condition to create any combination of conditions you need.

A silly example in the same test framework as above:

template<typename T>
typename std::enable_if<has_x<T>::as_method, float>::type getX(const T& v) {
    return v.x();
}

template<typename T>
typename std::enable_if<has_x<T>::as_member, float>::type getX(const T& v) {
    return v.x;
}

template<typename T>
void foo(const T& val) {
    std::cout << getX(val) << std::endl;
}

int main(int argc, char*argv[]) {
    fn_vec fn;
    mem_vec mem;

    foo(fn);
    foo(mem);

    return 0;
}

which outputs:

66
55

That should hopefully give you all tools you need to create your generic framework. Because everything is templated, most of it should get optimized-away by your compiler, and end up reasonably efficient.

All tested in GCC 4.8.2 on Linux, but it should work in any C++11 compiler.

Community
  • 1
  • 1
Martin Prazak
  • 1,476
  • 12
  • 20
  • this is great. Turns out that pikeyboom had a simpler solution for my Eigen/PCL example, but I have a few other situations where I could use the SFINAE approach. Thanks for the detailed example – brice rebsamen Oct 22 '15 at 17:50
1

If you don't want to fuss around with SFINAE, you can use the nice built in maps in PCL and use the Eigen cast function

Instead of constructing your Eigen::Vector3d type and passing it to f(), you could do the following

pcl::PointXYZ p;
f(p.getVector3fMap().cast<double>);

getVector3fMap() is available for all built in PCL types.

pikeyboom
  • 26
  • 2
  • I'm not sure but I think this will save you a construction operation as well since you are not constructing the temporary. – pikeyboom Oct 21 '15 at 13:49