Sergey Grigoryants
Sergey Grigoryants

Reputation: 156

AnyFunction class with type erasure

I want to implement a type-erasure class AnyFunction that would be able of storing any entity with templated call operator(that returns void). For example:

struct Printer {
  template<typename... Args>
  void operator()(Args&&... args) {
    std::cout << ... << std::forward<Args>(args);
  }
};

Printer typed_printer

AnyFunction printer(typed_printer);
printer(1,2.,"3");

should print 1 2 3. I understand how to do this for entities with the known signature of the call operator. For example, this works:

struct AddFunc {
void operator()(int a, int b) {
  std::cout << a << "+" << b << "=" << a + b << std::endl;
}
};


struct PrintSmthFunc {
void operator()(int a, double b, const char *c) {
  std::cout << a << b << c << std::endl;
}
};


int main() {
  AddFunc f1;
  AnyFunction addFunc(f1);
  addFunc(10, 20);
  PrintSmthFunc f2;
  AnyFunction printSmthFunc(f2);
  printSmthFunc(1, 2., "3");
}

(here is the implementation: https://godbolt.org/z/r4zq6f81G). But I don't understand how to do it in the general case of templated operator... I think that it is impossible to fully type-erase it.

Upvotes: 3

Views: 322

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275395

You can't quite do what you are asking for.

You can write a type AnyPrinter, but you cannot write an AnyFunction without literally shipping a C++ compiler and taking as input the C++ code you want to store in it and compiling a DLL on the fly.

The existence of DLLs should make the problem clear. Suppose your AnyFunction is defined in AnyFunction.h.

We compile Printer in a DLL PrinterCode.dll. We then compile some other type, call it Foo, in Foo.dll.

We pass AnyPrinter to an AnyFunction and return it from the PrinterCode.dll.

Then your executable loads both dlls, passes the AnyFunction to Foo.dll, which calls it with a private type Foo.

Where does the code that knows how to print Foo live?

The executable does not have access to Printer's source code. The PrinterCode.dll has no access to Foo. And Foo.dll has no access to Printer.

C++ binaries do not include data equivalent to the code you write. They contain a projection of that code to an abstract machine specified in the standard, then that is projected to machine code.

Other languages can pull this kind of stuff off because their runtime representation either includes everything about the code you wrote, or its generic code isn't as powerful as templates and is actually just automatic run time type casts wrappers.

...

Now, if you want something like Java generics, C++ can do this.

You can write a Printable, but it doesn't look like C++ template code.

struct Printer {
  void operator()(std::initializer_list<PrintableRef> args) {
    for(auto arg:args)
      std::cout << arg;
    }
};

I can get that to work. The trick is we do the work in PrintableRef.

PrintableRef type erases things down to "I can be printed". Here is a quick one:

struct PrintableRef {
  void(*print)(void*, std::ostream&) = nullptr;
  void* pdata = nullptr;

  template<class T> requires (!std::is_same_v<std::decay_t<T>, PrintableRef>)
  PrintableRef(T&& t):
    print([](void* pvoid, std::ostream& os) {
      os << *static_cast<decltype(std::addressof(t))>(pvoid);
    }),
    pdata(std::addressof(t))
  {}
  PrintableRef(PrintableRef const&)=default;
  ~PrintableRef()=default;
  PrintableRef()=delete;

  friend
  std::ostream& operator<<(std::ostream& os, PrintableRef self) {
    self.print(self.pvoid, os);
    return os;
  }
};

but this doesn't reach the point of AnyFunction.

Someone has to know both the type and the fact you want to print it at some point in your code.

Upvotes: 2

SergeyA
SergeyA

Reputation: 62583

Unfortunately, it is not possible in C++. In general, type-erasure works via run-time polymorphism, which requires virtual functions.

The only way to make your AnyFunction signature-agnostic would be to make it's operator() a template. But templates can not be virtuals. Hence, you are correct saying that it is not possible to "fully" type-erase it.

Upvotes: 2

Related Questions