Search code examples
c++boostboost-type-erasure

Simulating a Pure Virtual Template Member Function with Boost.TypeErasure


The documentation for Boost.TypeErasure includes an example "polymorphic range formatter" which simulates the concept of a "pure virtual template member function". I am able to compile and run that example code, which defines a class hierarchy that allows a sequence to be formatted in several different ways. I would like to pare it down to a much simpler example which simply accepts a value and sends it to std::out. Here is my naive attempt:

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/iterator.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/tuple.hpp>
#include <boost/type_erasure/same_type.hpp>
#include <iostream>

using namespace boost::type_erasure;

struct _x : placeholder {};

class abstract_printer2 {
public:
    template<class X>
    void print(X x) const {
        tuple<requirements, _x> args(x);
        do_print(get<0>(args));
    }
    virtual ~abstract_printer2() {}
protected:
    typedef boost::mpl::vector<
        ostreamable<std::ostream, _x>
    > requirements;
    typedef boost::type_erasure::any<requirements, _x> x_type;
    virtual void do_print(x_type x) const = 0;
};

class separator_printer2 : public abstract_printer2 {
protected:
    virtual void do_print(x_type x) const {
        std::cout << x << std::endl;
    }
};

int main() {
    separator_printer2 p4;
    int q = 5;
    p4.print(q);
}

When I try to compile that code in Visual Studio 2022, the build fails with:

Build started at 14:02...
1>------ Build started: Project: test, Configuration: Debug x64 ------
1>test.cpp
1>C:\bob\projects\test\test.cpp(17,17): error C2280: 'boost::type_erasure::any<abstract_printer2::requirements,_x>::any(const boost::type_erasure::any<abstract_printer2::requirements,_x> &)': attempting to reference a deleted function
1>C:\bob\projects\boost\boost\type_erasure\any.hpp(1928,1):
1>compiler has generated 'boost::type_erasure::any<abstract_printer2::requirements,_x>::any' here
1>C:\bob\projects\boost\boost\type_erasure\any.hpp(1928,1):
1>'boost::type_erasure::any<abstract_printer2::requirements,_x>::any(const boost::type_erasure::any<abstract_printer2::requirements,_x> &)': function was implicitly deleted because a base class invokes a deleted or inaccessible function 'boost::type_erasure::any_constructor_control<boost::type_erasure::any_constructor_impl<Concept,T>,void>::any_constructor_control(const boost::type_erasure::any_constructor_control<boost::type_erasure::any_constructor_impl<Concept,T>,void> &)'
1>        with
1>        [
1>            Concept=abstract_printer2::requirements,
1>            T=_x
1>        ]
1>C:\bob\projects\boost\boost\type_erasure\any.hpp(376,5):
1>'boost::type_erasure::any_constructor_control<boost::type_erasure::any_constructor_impl<Concept,T>,void>::any_constructor_control(const boost::type_erasure::any_constructor_control<boost::type_erasure::any_constructor_impl<Concept,T>,void> &)': function was explicitly deleted
1>        with
1>        [
1>            Concept=abstract_printer2::requirements,
1>            T=_x
1>        ]
1>C:\bob\projects\test\test.cpp(17,17):
1>the template instantiation context (the oldest one first) is
1>  C:\bob\projects\test\test.06.factory\test.cpp(38,7):
1>  see reference to function template instantiation 'void abstract_printer2::print<int>(X) const' being compiled
1>        with
1>        [
1>            X=int
1>        ]
1>Done building project "test.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 14:02 and took 01.517 seconds ==========

What am I doing wrong?


Solution

  • Your x_type is a type that supports ostreamable.

    You did not require copyable. You then tried to copy your x_type; this copy constructor is deleted.

    template<class X>
    void print(X x) const {
        tuple<requirements, _x> args(x); // <--- tuple of erased elements
        do_print(get<0>(args)); // <--- copy first element of tuple (error)
    }
    

    You need to either add "copy" to your type erasure requirements, or avoid copying.

    Once you do that, you get another error; you are destroying x_type without requiring it be destructible.

      template<class X>
      void print(X x) const {
        do_print(x_type(std::move(x))); // <-- avoids copying it
      }
      virtual ~abstract_printer2() {}
    protected:
      typedef boost::mpl::vector<
        ostreamable<std::ostream, _x>,
        destructible<_x> // <- added "can destroy it"
      > requirements;
    

    and there we go.

    Live example.

    A common thing I like using is a reference type instead of a value owning type. This gets rid of the destructable issue:

    class abstract_printer2 {
    public:
      template<class X>
      void print(X x) const {
        do_print(x_ref(x)); // <-- no move move
      }
      virtual ~abstract_printer2() {}
    protected:
      typedef boost::mpl::vector<
        ostreamable<std::ostream, _x>
      > requirements;
      typedef boost::type_erasure::any<requirements, _x&> x_ref; // <-- notice the & and renaming
      virtual void do_print(x_ref x) const = 0;
    };
    

    as if we don't plan on storing the objects, taking by reference works.

    You can also use the alias any_ref<requirements, _x> instead of any<requirements, _x&>.