0

Is it possible to extend boost::lexical_cast to handle other datatypes, without actually modifying those classes?

In my case, I want to extend it to handle things like cv::Point and cv::Point3, taking a string-separated list of coordinates and loading them.. So the ability to do something like:

cv::Point mypoint = boost::lexical_cast<cv::Point>("2,4");

The cv::Point class already has stream operators, but not compatible with istream and wstream, so it fails.

Edit

I ask this because I'm working in a framework with a templated function get_parameter that uses boost::lexical_cast to convert a string (read from a configuration file) into the desired datatype. It works great for ints & floats, but right now I have to call it multiple times to read a 2D or 3D point (or even worse, arrays of coefficients). It would be nice to be able to modify lexical_cast to handle these cases.

As such, this isn't specific to OpenCV, I just chose that as the simplest datatype.. I'm more interested in the general solution.

Edit 2

Here's a sample app that I've been trying:

#include <opencv2/opencv.hpp>
#include <boost/lexical_cast.hpp>

template <typename T>
std::istream& operator>>(std::istream& stream, cv::Point_<T> &p) {
   // Eventually something will go here
   // to put stream into p
}

int main(int argc, char **argv) {
  cv::Point_<float> p = boost::lexical_cast<cv::Point_<float>>(std::string("1,2"));
  std::cout << "p = " << p << std::endl;
  return 0;
}

And it fails with a beautiful C++ template error like so:

In file included from /home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:41:0,
             from /home/rhand/Development/experiments/lexical_Cast/test.cc:2:
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp: In instantiation of ‘struct boost::detail::deduce_target_char_impl<boost::detail::deduce_character_type_later<cv::Point_<float> > >’:
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:415:89:   required from ‘struct boost::detail::deduce_target_char<cv::Point_<float> >’
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:674:92:   required from ‘struct boost::detail::lexical_cast_stream_traits<std::basic_string<char>, cv::Point_<float> >’
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:2363:19:   required from ‘static Target boost::detail::lexical_cast_do_cast<Target, Source>::lexical_cast_impl(const Source&) [with Target = cv::Point_<float>; Source = std::basic_string<char>]’
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:2543:50:   required from ‘Target boost::lexical_cast(const Source&) [with Target = cv::Point_<float>; Source = std::basic_string<char>]’
/home/rhand/Development/experiments/lexical_Cast/test.cc:11:82:   required from here
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/static_assert.hpp:31:45: error: static assertion failed: Target type is neither std::istream`able nor std::wistream`able
 #     define BOOST_STATIC_ASSERT_MSG( ... ) static_assert(__VA_ARGS__)
                                         ^
/home/rhand/Development/mlx/ml_3rdparty/install/boost/include/boost/lexical_cast.hpp:388:13: note: in expansion of macro ‘BOOST_STATIC_ASSERT_MSG’
         BOOST_STATIC_ASSERT_MSG((result_t::value || boost::has_right_shift<std::basic_istream<wchar_t>, T >::value),
         ^
make[2]: *** [CMakeFiles/test.dir/test.cc.o] Error 1
make[1]: *** [CMakeFiles/test.dir/all] Error 2
make: *** [all] Error 2
Yeraze
  • 3,269
  • 4
  • 28
  • 42
  • 1
    Can't you provide your own `lexical_cast` specialization? – Chad Oct 08 '14 at 15:20
  • Why do you prefer coercing the behavior from lexical_cast as opposed to just creating a free function? I'm just curious. – Nir Friedman Oct 08 '14 at 15:20
  • The framework I'm working in has a templated function `get_parameter` that uses boost::lexical_cast internally to convert a string (Read from file) into the desired datatype.. Right now I have to manually read X & Y separately, it would be convenient to simply read the cv::Point. – Yeraze Oct 08 '14 at 15:22

2 Answers2

1

If memory serves right, you should be able to define a free function (istream& operator>>(istream&, cv::Point&)) that lexical_cast will use, as long as it is defined before the lexical_cast call.

Edit: see Overloading istream operator>> c++ for an example

Edit 2: Here's a working example (VS2013, boost 1.44 [yep, I need to update!]). Could you please post a minimal example that fails in your case?

#include <iostream>
#include <boost/lexical_cast.hpp>

template <typename ElementType>
struct Point
{
    Point() : x(0), y(0) {}
    ElementType x, y;
};

template <typename ElementType>
std::istream& operator>>(std::istream& stream, Point<ElementType> &p)
{
    stream >> p.x;
    stream.get();
    stream >> p.y;
    return stream;
}

template <typename ElementType>
std::ostream& operator<<(std::ostream& stream, const Point<ElementType> &p)
{
    stream << p.x << "," << p.y;
    return stream;
}

int main(int argc, char **argv)
{
    Point<int> p = boost::lexical_cast<Point<int>>("1,2");
    std::cout << "p=[" << p << "]";
    std::cin.get();
    return 0;
}

Edit 3: made the Point class a templated one, since it's seems to be your case

Edit 4: with OpenCV 2.4.10, also works on aforementioned setup:

#include <iostream>
#include <boost/lexical_cast.hpp>
#include <opencv2/opencv.hpp>

template <typename ElementType>
std::istream& operator>>(std::istream& stream, cv::Point_<ElementType> &p)
{
    stream >> p.x;
    stream.get();
    stream >> p.y;
    return stream;
}

int main(int argc, char **argv)
{
    auto cv_p = boost::lexical_cast<cv::Point_<float>>(std::string("1,2"));
    std::cout << "opencv p=" << cv_p << "";
    std::cin.get();
    return 0;
}
Community
  • 1
  • 1
Denys Bulant
  • 316
  • 4
  • 10
  • hrm.. Doesn't seem to work, I added a stub "template cv::Point_& operator<<(std::istream& is, std::string &is)" and a stub "template std::istream& operator>>(std::istream& is, cv::Point_ &p)", and it still complains that it's not compatible with istream & wstream. – Yeraze Oct 08 '14 at 16:02
  • @Yeraze hem, sorry, I don't remember the right way then... I'll search when I'll get home, if the solution isn't posted until then. – Denys Bulant Oct 08 '14 at 16:15
  • @Yeraze I've added a minimal example, though not using OpenCV's Point. Could you please check if this simple case works on your toolsuite? – Denys Bulant Oct 08 '14 at 18:47
  • Still doesn't seem to work :-/ I posted my failure example and error message. – Yeraze Oct 08 '14 at 21:44
  • @Yeraze You might want to make the test code complete. Of particular interest is the fact that your compiler doesn't complain about the missing istream return in your operator>> overload. Also, can you try my simple version, and only it? – Denys Bulant Oct 08 '14 at 22:19
  • @Yeraze That's weird; even boost's test case use this approach: http://www.boost.org/doc/libs/1_55_0/libs/conversion/lexical_cast_test.cpp (`void operators_overload_test()`) Is your compiler relatively recent? – Denys Bulant Oct 08 '14 at 22:36
  • I can't get your version to work either.. I'm using gcc4.8.2 with -std=c++11 . – Yeraze Oct 09 '14 at 01:18
1

You can define a specialization of boost::lexical_cast for the types that you want to convert.

Toy example:

typedef struct { int x; int y; } Point;

namespace boost {
    template<>
      std::string lexical_cast(const Point& arg) { return "2,3"; }
}

int main () {
    std::cout << boost::lexical_cast<std::string>(Point ()) << std::endl;
    }

prints 2,3.

Going from a string to a point requires a bit more work, but you can see how to do it.

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45