boost-ext / te

C++17 Run-time Polymorphism (Type Erasure) library
451 stars 38 forks source link

How to return `std::ostream&` from type erased function? #13

Closed redboltz closed 6 years ago

redboltz commented 6 years ago

I wrote the following example based on README.md.

#include <iostream>
#include <vector>

#include <boost/te.hpp>

namespace te = boost::te;

struct Drawable : te::poly<Drawable> {
  using te::poly<Drawable>::poly;

  void draw(std::ostream& out) const {
    te::call([](auto const& self, auto& out) { self.draw(out); }, *this, out);
  }
};

struct Square {
  void draw(std::ostream& out) const { out << "Square"; }
};

struct Circle {
  void draw(std::ostream& out) const { out << "Circle"; }
};

int main() {
  std::vector<Drawable> drawables;

  drawables.push_back(Square{});
  drawables.push_back(Circle{});

  for (auto const& drawable : drawables) {
    drawable.draw(std::cout); // prints Square Circle
  }
}

It works fine. https://wandbox.org/permlink/qkfHT6Zg3Mk9qbCS

I tried to modify

void draw(std::ostream& out)

to

std::ostream& draw(std::ostream& out)

Then I got errors.

https://wandbox.org/permlink/YbEudxsv1oVSgmgC

#include <iostream>
#include <vector>

#include <boost/te.hpp>

namespace te = boost::te;

struct Drawable : te::poly<Drawable> {
  using te::poly<Drawable>::poly;

  std::ostream& draw(std::ostream& out) const {
    return te::call<std::ostream&>([](auto const& self, auto& out) { self.draw(out); }, *this, out);
  }
};

struct Square {
  std::ostream& draw(std::ostream& out) const { return out << "Square"; }
};

struct Circle {
  std::ostream& draw(std::ostream& out) const { return out << "Circle"; }
};

int main() {
  std::vector<Drawable> drawables;

  drawables.push_back(Square{});
  drawables.push_back(Circle{});

  for (auto const& drawable : drawables) {
    drawable.draw(std::cout) << std::endl; // prints Square Circle
  }
}
In file included from prog.cc:4:
/opt/wandbox/te/include/boost/te.hpp:247:10: error: call to deleted constructor of 'std::__1::basic_ostream<char>'
  return reinterpret_cast<R (*)(void *, Ts...)>(self.vptr[N - 1])(
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/wandbox/te/include/boost/te.hpp:271:18: note: in instantiation of function template specialization 'boost::te::v1::detail::call_impl<Drawable, 1, std::__1::basic_ostream<char> &, (lambda at prog.cc:12:36), std::__1::basic_ostream<char> &>' requested here
  return detail::call_impl<I>(
                 ^
prog.cc:12:16: note: in instantiation of function template specialization 'boost::te::v1::call<std::__1::basic_ostream<char> &, 0, (lambda at prog.cc:12:36), Drawable, std::__1::basic_ostream<char> &>' requested here
    return te::call<std::ostream&>([](auto const& self, auto& out) { self.draw(out); }, *this, out);
               ^
/opt/wandbox/clang-6.0.1/include/c++/v1/ostream:181:5: note: 'basic_ostream' has been explicitly marked deleted here
    basic_ostream           (const basic_ostream& __rhs) = delete;
    ^
prog.cc:12:12: error: non-const lvalue reference to type 'basic_ostream<...>' cannot bind to a temporary of type 'basic_ostream<...>'
    return te::call<std::ostream&>([](auto const& self, auto& out) { self.draw(out); }, *this, out);
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.

It seems that te tries to copy std::ostream.

If I changed the return value type from std::ostream& to std::ostream* , then the code works as I expected.

https://wandbox.org/permlink/BexYhlEqmbgam6AV

#include <iostream>
#include <vector>

#include <boost/te.hpp>

namespace te = boost::te;

struct Drawable : te::poly<Drawable> {
  using te::poly<Drawable>::poly;

  std::ostream* draw(std::ostream& out) const {
    return te::call<std::ostream*>([](auto const& self, auto& out) { 
      self.draw(out);
      return &out;
    }, *this, out);
  }
};

struct Square {
  std::ostream* draw(std::ostream& out) const { out << "Square"; return &out; }
};

struct Circle {
  std::ostream* draw(std::ostream& out) const { out << "Circle"; return &out; }
};

int main() {
  std::vector<Drawable> drawables;

  drawables.push_back(Square{});
  drawables.push_back(Circle{});

  for (auto const& drawable : drawables) {
    *drawable.draw(std::cout) << std::endl; // prints Square Circle
  }
}

I noticed that in this case, std::reference_wrapper might help. I updated the reference version as follows:

https://wandbox.org/permlink/wJ5HBSmk2eGiMMZ5

#include <iostream>
#include <vector>

#include <boost/te.hpp>

namespace te = boost::te;

struct Drawable : te::poly<Drawable> {
  using te::poly<Drawable>::poly;

  std::ostream& draw(std::ostream& out) const {
    //              use reference_wrapper here
    return te::call<std::reference_wrapper<std::ostream>>([](auto const& self, auto& out) { self.draw(out); }, *this, out);
  }
};

struct Square {
  std::ostream& draw(std::ostream& out) const { return out << "Square"; }
};

struct Circle {
  std::ostream& draw(std::ostream& out) const { return out << "Circle"; }
};

int main() {
  std::vector<Drawable> drawables;

  drawables.push_back(Square{});
  drawables.push_back(Circle{});

  for (auto const& drawable : drawables) {
    drawable.draw(std::cout) << std::endl; // prints Square Circle
  }
}

I think that std::reference_wrapper version is elegant.

Is this a right way to return reference from te::call ?

redboltz commented 6 years ago

I noticed that my code has some mistakes.

std::ostream& version should be https://wandbox.org/permlink/XYopHIUqt44OCYDO It outputs compilation errors.

std::reference_wrapper<std::ostream>> version should be https://wandbox.org/permlink/HjbLE8WkLgGEK6Kd It outputs expected results.

redboltz commented 6 years ago

I wrote lifetime trace code and it works as I expected. https://wandbox.org/permlink/YnSyXpchg8k2nT6V

So I can use the following pattern to return the reference that is passed as the argument.

  T& mem_func(T& t) const {
    return te::call<std::reference_wrapper<T>>(
      [](auto const& self, auto& t) { 
        return std::ref(self.mem_func(t));
      },
      *this,
      t);
  }