问题
Background
The title probably sounds confusing, so let me explain. First of all, here is a minimal version of my implementation, so you can follow along with the concepts more easily. If you've seen some of Sean Parent's talks, you'll know he came up with a way to abstract polymorphism, allowing code such as this:
std::vector<Drawable> figures{Circle{}, Square{}};
for (auto &&figure : figures) {draw(figure);}
Notice that there are no pointers or anything. Calling draw
on a Drawable
will call the appropriate draw
function on the contained object without the type of the object being easily accessible. One major downside to this is that similar classes to Drawable
have to be written for each task. I'm trying to abstract this a bit so that the function does not have to be known by the class. My current solution is as follows:
std::vector<Applicator<Draw>> figures{Circle{}, Square{}};
for (auto &&figure : figures) {figure.apply(Draw{});}
Here, Draw
is a functor with an operator()(Circle)
and opeator()(Square)
, or a generic version. In this way, this is also sort of a visitor pattern implementation. If you wanted to also, say, print the name of each figure, you could do Applicator<Draw, PrintName>
. When calling apply
, the desired function is chosen.
My implementation works by passing a boost::variant
of the callable types to the virtual function and having it visit that variant and call the function within. Overall, I would say this implementation is acceptable, but I haven't yet thought much about allowing any number of parameters or a return type, let alone ones that differ from function to function.
Question
I spent days trying to think of a way to have this work without making Applicator
a template. Ideally, the use would be more similar to this. For the sake of simplicity, assume the functions called must have the signature void(ObjectType)
.
//For added type strictness, I could make this Applicator<Figure> and have
//using Figure<struct Circle> = Circle; etc
std::vector<Applicator> figures{Circle{}, Square{}};
for (auto &&figure : figures) {figure.apply(Draw{});} //or .apply(draw); if I can
The problem usually comes down to the fact that the type of the object can only be obtained within a function called on it. Internally, the class uses virtual functions, which means no templates. When apply
is called, here's what happens (identical to Sean's talks):
- The internal base class's apply is called on a pointer to the base class with the runtime type of a derived class.
- The call is dispatched to the derived class, which knows the type of the stored object.
So by the time I have the object, the function to call must be reduced to a single type known within the class that both knows which function to call and takes the object. I cannot for the life of me come up with a way to do this.
Attempts
Here are a couple of failed attempts so you can see why I find this difficult:
The premise for both of the first two is to have a type that holds a function call minus the unknown first argument (the stored object). This would need to at least be templated on the type of the callable object. By using Sean Parent's technique, it's easy enough to make a FunctionCall<F>
class that can be stored in a GenericFunctionCall
, much like a Circle
in a Figure
. This GenericFunctionCall
can be passed into the virtual function, whereas the other cannot.
Attempt 1
apply()
is called with a known callable object type.- The type of the callable object is used to create a
FunctionCall<Type>
and store it as a type-erasedGenericFunctionCall
. - This
GenericFunctionCall
object is passed to the virtualapply
function. - The derived class gets the call object and has the object to be used as the first argument available.
- For the same reason of virtual functions not being allowed to be templates, the
GenericFunctionCall
could call the necessary function on the rightFunctionCall<Type>
, but not forward the first (stored object) argument.
Attempt 2
As a continuation of attempt 1:
- In order to pass the stored object into the function called on the
GenericFunctionCall
, the stored object could be type-erased into aGenericObject
. - One of two things would be possible:
- A function is called and given a proper
FunctionCall<Type>
, but has aGenericObject
to give to it, with the type unknown outside of a function called on it. Recall that the function cannot be templated on the function call type. - A function is called and given a proper
T
representing the stored object, but has aGenericFunctionCall
to extract the right function call type from. We're back where we started in the derived class'sapply
function.
- A function is called and given a proper
Attempt 3
- Take the known type of a callable object when calling
apply
and use it to make something that stores a function that it can call with a known stored object type (likestd::function
). - Type-erase that into a
boost::any
and pass it to the virtual function. - Cast it back to the appropriate type when the stored object type is known in the derived class and then pass the object in.
- Realize that this whole approach requires the stored object type to be known when calling
apply
.
Are there any bright ideas out there for how to turn this class into one that doesn't need the template arguments, but can rather take any callable object and call it with the stored object?
P.S. I'm open for suggestions on better names than Applicator
and apply
.
回答1:
This is not possible. Consider a program composed of three translation units:
// tu1.cpp
void populate(std::vector<Applicator>& figures) {
figures.push_back(Circle{});
figures.push_back(Square{});
}
// tu2.cpp
void draw(std::vector<Applicator>& figures) {
for (auto &&figure : figures) { figure.apply(Draw{}); }
}
// tu3.cpp
void combine() {
std::vector<Applicator>& figures;
populate(figures);
draw(figures);
}
It must be possible for each TU to be translated separately, indeed in causal isolation. But this means that at no point is there a compiler that simultaneously has access to Draw
and to Circle
, so code for Draw
to call Circle::draw
can never be generated.
来源:https://stackoverflow.com/questions/26303718/how-can-i-make-a-class-that-type-erases-objects-until-a-function-is-called-on-th