Reputation: 156
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
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
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