Is it possible to perform double dispatch with runtime polymorphism?
Say I have some classes, and some of those classes can be added/multiplied/etc., and I want to store those dynamically within another class that performs type erasure at runtime. Then say I want to perform basic operations on the data held within that class.
The way to handle this (as far as I'm aware) is to use double dispatch to specialize the operation. However, all of the solutions I have encountered rely on the fact that you have a numerable amount of types, and then use virtual function calls or dynamic_casts, if-else, and RTTI to deduce the type at runtime. Because the data held within the class isn't known until runtime, I can't create a bunch of virtual methods or do a brute force check on the types. So I figured the visitor pattern would be the best solution, but even then, I can't seem to get whether or not this is possible.
I have a wrapper class that holds a smart pointer to a nested polymorphic class to implement the type erasure and runtime polymorphism, but I can't figure out if it's possible to use the visitor pattern to do double dispatch on this.
Note that the code below is incomplete, it just shows my thought process.
class Wrapper {
private:
class Concept;
template<typename T> class Model;
class BaseVisitor {
public:
virtual ~Visitor() = default;
virtual void visit(Concept &) = 0;
};
template<typename T>
class Visitor : public BaseVisitor {
private:
T first_;
public:
Visitor(T first) : first_(first) {}
virtual void visit(Concept &other) override {
// perform addition
}
};
class Concept {
public:
virtual ~Concept() = default;
virtual void add(Concept &m) const = 0;
virtual void accept(BaseVisitor &visitor) const = 0;
};
template<typename T>
class Model final : public Concept {
private:
T data_;
public:
Model(T m)
: data_(m) {}
virtual void add(Concept &m) const override {
Visitor<T> v(data_);
m.accept(v);
};
virtual void accept(BaseVisitor &visitor) const override {
visitor.visit(*this);
};
};
std::shared_ptr<const Concept> ptr_;
// This isn't right, it just illustrates what I'm trying to do.
// friend Something operator+(Wrapper &lhs, Wrapper &rhs) {
// return (*lhs.ptr_).add(*rhs.ptr_);
// }
public:
template<typename T>
Wrapper(T value) : ptr_(std::make_shared<Model<T>>(value)) {}
};
I've looked into implementing double dispatch using function pointers, template specialization, and static type IDs as well, but I can't seem to figure out how to make it work.
Is this even possible?
EDIT
Based on the comments below, in order to be more specific and to give a little more background, I am using templated classes that use template functions to perform basic operations like addition and multiplication. However, I would also like to store those templated classes within a vector, hence the type erasure. Now, if I wanted to do operations on those classes after I perform the type erasure, I need some way to deduce the type for the templated function. However, since I can't easily get the internal held type back from the Wrapper, I am hoping that there is a way I can call the correct template function on the data held within the Wrapper::Model<T> class, whether that is a visitor pattern, static type IDs, whatever.
To be even more specific, I am working with classes to do delayed evaluation and symbolic computations, meaning I have classes such as Number<T>, which can be Number<int>, Number<double>, etc. and classes such as Variable, Complex<T> and all of the TMP combinations for various operations, such as Add<Mul<Variable, Variable>, Number<double>>, etc.
I can work with all of these fine at compile-time, but then I need to be able to store these in a vector -- something like std::vector<Wrapper> x = {Number<int>, Variable, Add<Number<double>, Variable>};. My best guess at this was to perform type erasure to store the expressions inside the polymorphic Wrapper. This serves double-duty to enable runtime parsing support of symbolic expressions.
However, the functions I wrote to handle the addition, such as
template<typename T1, typename T2>
const Add<T1, T2> operator+(const T1 &lhs, const T2 &rhs)
{ return Add<T1, T2>(lhs, rhs); }
can't accept Wrapper and pull the type out (due to the type erasure). I can, however, insert the Wrapper into the Add expression class, meaning I can carry around the hidden types. The problem is when I actually get down to evaluating the result of something like Add<Wrapper, Wrapper>. In order to know what this comes out to, I'd need to figure out what's actually inside or to do something along the lines of double dispatch.
The main problem is that the examples for double dispatch that most closely match my problem, like this question on SO, rely on the fact that I can write out all of the classes, such as Shapes, Rectangles. Since I can't explicitly do that, I'm wondering if there's a way to perform double dispatch to evaluate the expression based on the data held inside the Model<T> class above.
Related
tl;dr
My goal is to conditionally provide implementations for abstract virtual methods in an intermediate workhorse template class (depending on template parameters), but to leave them abstract otherwise so that classes derived from the template are reminded by the compiler to implement them if necessary.
I am also grateful for pointers towards better solutions in general.
Long version
I am working on an extensible framework to perform "operations" on "data". One main goal is to allow XML configs to determine program flow, and allow users to extend both allowed data types and operations at a later date, without having to modify framework code.
If either one (operations or data types) is kept fixed architecturally, there are good patterns to deal with the problem. If allowed operations are known ahead of time, use abstract virtual functions in your data types (new data have to implement all required functionality to be usable). If data types are known ahead of time, use the Visitor pattern (where the operation has to define virtual calls for all data types).
Now if both are meant to be extensible, I could not find a well-established solution.
My solution is to declare them independently from one another and then register "operation X for data type Y" via an operation factory. That way, users can add new data types, or implement additional or alternative operations and they can be produced and configured using the same XML framework.
If you create a matrix of (all data types) x (all operations), you end up with a lot of classes. Hence, they should be as minimal as possible, and eliminate trivial boilerplate code as far as possible, and this is where I could use some inspiration and help.
There are many operations that will often be trivial, but might not be in specific cases, such as Clone() and some more (omitted here for "brevity"). My goal is to conditionally provide implementations for abstract virtual methods if appropriate, but to leave them abstract otherwise.
Some solutions I considered
As in example below: provide default implementation for trivial operations. Consequence: Nontrivial operations need to remember to override with their own methods. Can lead to run-time problems if some future developer forgets to do that.
Do NOT provide defaults. Consequence: Nontrivial functions need to be basically copy & pasted for every final derived class. Lots of useless copy&paste code.
Provide an additional template class derived from cOperation base class that implements the boilerplate functions and nothing else (template parameters similar to specific operation workhorse templates). Derived final classes inherit from their concrete operation base class and that template. Consequence: both concreteOperationBase and boilerplateTemplate need to inherit virtually from cOperation. Potentially some run-time overhead, from what I found on SO. Future developers need to let their operations inherit virtually from cOperation.
std::enable_if magic. Didn't get the combination of virtual functions and templates to work.
Here is a (fairly) minimal compilable example of the situation:
//Base class for all operations on all data types. Will be inherited from. A lot. Base class does not define any concrete operation interface, nor does it necessarily know any concrete data types it might be performed on.
class cOperation
{
public:
virtual ~cOperation() {}
virtual std::unique_ptr<cOperation> Clone() const = 0;
virtual bool Serialize() const = 0;
//... more virtual calls that can be either trivial or quite involved ...
protected:
cOperation(const std::string& strOperationID, const std::string& strOperatesOnType)
: m_strOperationID()
, m_strOperatesOnType(strOperatesOnType)
{
//empty
}
private:
std::string m_strOperationID;
std::string m_strOperatesOnType;
};
//Base class for all data types. Will be inherited from. A lot. Does not know any operations that might be performed on it.
struct cDataTypeBase
{
virtual ~cDataTypeBase() {}
};
Now, I'll define an example data type.
//Some concrete data type. Still does not know any operations that might be performed on it.
struct cDataTypeA : public cDataTypeBase
{
static const std::string& GetDataName()
{
static const std::string strMyName = "cDataTypeA";
return strMyName;
}
};
And here is an example operation. It defines a concrete operation interface, but does not know the data types it might be performed on.
//Some concrete operation. Does not know all data types it might be expected to work on.
class cConcreteOperationX : public cOperation
{
public:
virtual bool doSomeConcreteOperationX(const cDataTypeBase& dataBase) = 0;
protected:
cConcreteOperationX(const std::string& strOperatesOnType)
: cOperation("concreteOperationX", strOperatesOnType)
{
//empty
}
};
The following template is meant to be the boilerplate workhorse. It implements as much trivial and repetitive code as possible and is provided alongside the concrete operation base class - concrete data types are still unknown, but are meant to be provided as template parameters.
//ConcreteOperationTemplate: absorb as much common/trivial code as possible, so concrete derived classes can have minimal code for easy addition of more supported data types
template <typename ConcreteDataType, typename DerivedOperationType, bool bHasTrivialCloneAndSerialize = false>
class cConcreteOperationXTemplate : public cConcreteOperationX
{
public:
//Can perform datatype cast here:
virtual bool doSomeConcreteOperationX(const cDataTypeBase& dataBase) override
{
const ConcreteDataType* pCastData = dynamic_cast<const ConcreteDataType*>(&dataBase);
if (pCastData == nullptr)
{
return false;
}
return doSomeConcreteOperationXOnCastData(*pCastData);
}
protected:
cConcreteOperationXTemplate()
: cConcreteOperationX(ConcreteDataType::GetDataName()) //requires ConcreteDataType to have a static method returning something appropriate
{
//empty
}
private:
//Clone can be implemented here via CRTP
virtual std::unique_ptr<cOperation> Clone() const override
{
return std::unique_ptr<cOperation>(new DerivedOperationType(*static_cast<const DerivedOperationType*>(this)));
}
//TODO: Some Magic here to enable trivial serializations, but leave non-trivials abstract
//Problem with current code is that virtual bool Serialize() override will also be overwritten for bHasTrivialCloneAndSerialize == false
virtual bool Serialize() const override
{
return true;
}
virtual bool doSomeConcreteOperationXOnCastData(const ConcreteDataType& castData) = 0;
};
Here are two implementations of the example operation on the example data type. One of them will be registered as the default operation, to be used if the user does not declare anything else in the config, and the other is a potentially much more involved non-default operation that might take many additional parameters into account (these would then have to be serialized in order to be correctly re-instantiated on the next program run). These operations need to know both the operation and the data type they relate to, but could potentially be implemented at a much later time, or in a different software component where the specific combination of operation and data type are required.
//Implementation of operation X on type A. Needs to know both of these, but can be implemented if and when required.
class cConcreteOperationXOnTypeADefault : public cConcreteOperationXTemplate<cDataTypeA, cConcreteOperationXOnTypeADefault, true>
{
virtual bool doSomeConcreteOperationXOnCastData(const cDataTypeA& castData) override
{
//...do stuff...
return true;
}
};
//Different implementation of operation X on type A.
class cConcreteOperationXOnTypeASpecialSauce : public cConcreteOperationXTemplate<cDataTypeA, cConcreteOperationXOnTypeASpecialSauce/*, false*/>
{
virtual bool doSomeConcreteOperationXOnCastData(const cDataTypeA& castData) override
{
//...do stuff...
return true;
}
//Problem: Compiler does not remind me that cConcreteOperationXOnTypeASpecialSauce might need to implement this method
//virtual bool Serialize() override {}
};
int main(int argc, char* argv[])
{
std::map<std::string, std::map<std::string, std::unique_ptr<cOperation>>> mapOpIDAndDataTypeToOperation;
//...fill map, e.g. via XML config / factory method...
const cOperation& requestedOperation = *mapOpIDAndDataTypeToOperation.at("concreteOperationX").at("cDataTypeA");
//...do stuff...
return 0;
}
if you data types are not virtual (for each operation call you know both operation type and data type at compile time) you may consider following approach:
#include<iostream>
#include<string>
template<class T>
void empty(T t){
std::cout<<"warning about missing implementation"<<std::endl;
}
template<class T>
void simple_plus(T){
std::cout<<"simple plus"<<std::endl;
}
void plus_string(std::string){
std::cout<<"plus string"<<std::endl;
}
template<class Data, void Implementation(Data)>
class Operation{
public:
static void exec(Data d){
Implementation(d);
}
};
#define macro_def(OperationName) template<class T> class OperationName : public Operation<T, empty<T>>{};
#define macro_template_inst( TypeName, OperationName, ImplementationName ) template<> class OperationName<TypeName> : public Operation<TypeName, ImplementationName<TypeName>>{};
#define macro_inst( TypeName, OperationName, ImplementationName ) template<> class OperationName<TypeName> : public Operation<TypeName, ImplementationName>{};
// this part may be generated on base of .xml file and put into .h file, and then just #include generated.h
macro_def(Plus)
macro_template_inst(int, Plus, simple_plus)
macro_template_inst(double, Plus, simple_plus)
macro_inst(std::string, Plus, plus_string)
int main() {
Plus<int>::exec(2);
Plus<double>::exec(2.5);
Plus<float>::exec(2.5);
Plus<std::string>::exec("abc");
return 0;
}
Minus of this approach is that you'd have to compile project in 2 steps: 1) transform .xml to .h 2) compile project using generated .h file. On plus side compiler/ide (I use qtcreator with mingw) gives warning about unused parameter t in function
void empty(T t)
and stack trace where from it was called.
I am trying to write a class that I can store and use type information in without the need for a template parameter.
I want to write something like this:
class Example
{
public:
template<typename T>
Example(T* ptr)
: ptr(ptr)
{
// typedef T EnclosedType; I want this be a avaialable at the class level.
}
void operator()()
{
if(ptr == NULL)
return;
(*(EnclosedType*)ptr)(); // so i can cast the pointer and call the () operator if the class has one.
}
private:
void* ptr;
}
I am not asking how to write an is_functor() class.
I want to know how to get type information in a constructor and store it at the class level. If that is impossible, a different solution to this would be appreciated.
I consider this as a good and valid question, however, there is no general solution beside using a template parameter at the class level. What you tried to achieve in your question -- using a typedef inside a function and then access this in the whole class -- is not possible.
Type erasure
Only if you impose certain restrictions onto your constructor parameters, there are some alternatives. In this respect, here is an example of type erasure where the operator() of some given object is stored inside a std::function<void()> variable.
struct A
{
template<typename T>
A(T const& t) : f (std::bind(&T::operator(), t)) {}
void operator()() const
{
f();
}
std::function<void()> f;
};
struct B
{
void operator()() const
{
std::cout<<"hello"<<std::endl;
}
};
int main()
{
A(B{}).operator()(); //prints "hello"
}
DEMO
Note, however, the assumptions underlying this approach: one assumes that all passed objects have an operator of a given signature (here void operator()) which is stored inside a std::function<void()> (with respect to storing the member-function, see here).
Inheritance
In a sense, type erasure is thus like "inheriting without a base class" -- one could instead use a common base class for all constructor parameter classes with a virtual bracket operator, and then pass a base class pointer to your constructor.
struct A_parameter_base
{
void operator()() const = 0;
};
struct B : public A_parameter_base
{
void operator()() const { std::cout<<"hello"<<std::endl; }
};
struct A
{
A(std::shared_ptr<A_parameter_base> _p) : p(_p) {}
void operator()()
{
p->operator();
}
std::shared_ptr<A_parameter_base> p;
}
That is similar to the code in your question, only that it does not use a void-pointer but a pointer to a specific base class.
Both approaches, type erasure and inheritance, are similar in their applications, but type erasure might be more convenient as one gets rid of a common base class. However, the inheritance approach has the further advantage that you can restore the original object via multiple dispatch
This also shows the limitations of both approaches. If your operator would not be void but instead would return some unknown varying type, you cannot use the above approach but have to use templates. The inheritance parallel is: you cannot have a virtual function template.
The practical answer is to store either a copy of your class, or a std::ref wrapped pseudo-reference to your class, in a std::function<void()>.
std::function type erases things it stores down to 3 concepts: copy, destroy and invoke with a fixed signature. (also, cast-back-to-original-type and typeid, more obscurely)
What it does is it remembers, at construction, how to do these operations to the passed in type, and stores a copy in a way it can perform those operations on it, then forgets everything else about the type.
You cannot remember everything about a type this way. But almost any operation with a fixed signature, or which can be intermediaried via a fixed signature operation, can be type erased down to.
The first typical way to do this are to create a private pure interface with those operations, then create a template implementation (templated on the type passed to the ctor) that implements each operation for that particular type. The class that does the type erasure then stores a (smart) pointer to the private interface, and forwards its public operations to it.
A second typical way is to store a void*, or a buffer of char, and a set of pointers to functions that implement the operations. The pointers to functions can be either stored locally in the type erasing class, or stored in a helper struct that is created statically for each type erased, and a pointer to the helper struct is stored in the type erasing class. The first way to store the function pointers is like C-style object properties: the second is like a manual vtable.
In any case, the function pointers usually take one (or more) void* and know how to cast them back to the right type. They are created in the ctor that knows the type, either as instances of a template function, or as local stateless lambdas, or the same indirectly.
You could even do a hybrid of the two: static pimpl instance pointers taking a void* or whatever.
Often using std::function is enough, manually writing type erasure is hard to get right compared to using std::function.
Another version to the first two answers we have here - that's closer to your current code:
class A{
public:
virtual void operator()=0;
};
template<class T>
class B: public A{
public:
B(T*t):ptr(t){}
virtual void operator(){(*ptr)();}
T*ptr;
};
class Example
{
public:
template<typename T>
Example(T* ptr)
: a(new B<T>(ptr))
{
// typedef T EnclosedType; I want this be a avaialable at the class level.
}
void operator()()
{
if(!a)
return;
(*a)();
}
private:
std::unique_ptr<A> a;
}
I'm starting working with C++ templates just because I wanted to understand specific differences with other languages (Java) and I reached a point in which they started to diverge but I'm not getting how I am supposed to solve the specific problem (or get around it).
Suppose I have a generic value class, eg
template <class T>
class Value
{
protected:
T value;
public:
Value(Type type, T value) : type(type), value(value) {}
void set(T value) { this->value = value; }
T get() const { return this->value; }
T clone() { return new Value<T>(type, value); }
virtual string svalue() const = 0;
const Type type;
};
and a specific subtype:
class Int : public Value<int>
{
public:
Int(int value) : Value<int>(INT, value) { };
virtual string svalue() const { ... }
friend ostream& operator<<(ostream& os, const Int& v);
};
(I know it is also possible to specify type specific code by using template <> but since I still need to use it enough to understand it I just defined by own Int class for now, which is nothing more that a typedef Value<int> in the end)
Is it possible to have, let's say, a collection that is able to store arbitrary pointers to Value instances? Without the need of specifying the specific concrete type of the generic class.
From what I understand templates are just a compile time issue for which the compiler analyzes all the concrete types for which the template is used and compiles different versions of the same methods for each of them, thus what I'm trying to do doesn't seem to be possible (while in Java I am allowed to use wildcards for something like List<Value<?>>). Am I wrong?
Is there a common design to solve this issue or I am forced to drop templates to achieve it?
#include <iostream>
#include <memory>
class Base
{
public: virtual void Print() = 0;
};
template<typename T>
class Derived : public Base
{
T V;
public:
void Print() { std::cout << V; }
Derived(T v) : V(v) { }
};
int main()
{
std::unique_ptr<Base> Ptr (new Derived<int>(5));
Ptr->Print();
return 0;
}
I think it's pretty self-explanatory.
Is it possible to have, let's say, a collection that is able to store
arbitrary pointers to Value instances?
No, not in the way you seem to want. This is not possible:
template <class T>
class Value
{
// ...
};
vector<Value> my_values_;
This isn't possible because Value isn't a type -- it's really just a blueprint, and idea, if you will. Philisophical ramblings aside, you can't store ideas, you can only store things. A Value isn't a thing.
If this is what you want, then templates might be the wrong tool for the job. Wheat you might really be after are Abstract Base Classes in which the base class (say, class Value) defines the interface and the subclasses (say, class Int : public Value) define concrete types. That way, you can create containers of generic Values, using pointers:
vector<Value*> my_values_;
Or, better yet using smart pointers:
vector<unique_ptr<Value>> my_values_;
The Java technique can be done in C++ via a mixture of a common base class (see other answer by Bartek) and techniques like type erasure.
The C++ version, where values are actually values, cannot be done in Java. It can be done in some languages that compile to Java byte code if I recall correctly.
In Java the only objects you can get ahold of are actually more like garbage collected pointers to objects in C++. Actual instances of actual objects being directly stored or referred to is verbotin, because that gets in the way of Java style garbage collection.
So a container of Value<?> in Java is analogous to a container of pointers to a common base class of all Value types that is garbage collected in C++. Access to each instance then involves a dynamic_cast or static_cast equivalent in Java.
For a more Java esque behavior, give Value a common base with a virtual trivial destructor, pure virtual common methods that have the same signature over all instances, template version that implements things with different signatures, and factory functions that produce shared_ptr s to Value instances.
Use containers of shared_ptr to the Value base and use the dynamic shared ptr cast to get particular interfaces if you need them.
Now all of that means your code is 10 to 100 times slower than without all that structure, but it may still be faster than the equivalent Java version. And you have the option to not use it if you do not need it.
I always love to confuse matters and throw in a nice syntactic twist although it still just does the same (using a common base class). The only odd bit is that the base class of Value<T> is spelled Value<> and can be used as such in a container (although not directly, of course, since you need use a point to avoid slicing):
#include <memory>
#include <vector>
template <typename T = void>
class Value;
template <>
class Value<void>
{
public:
virtual ~Value() {}
};
template <typename T>
class Value
: public Value<>
{
T value_;
public:
Value(T value): value_(value) {}
// whatever
};
template <typename T>
std::unique_ptr<Value<T>> make_value(T value) {
return std::unique_ptr<Value<T>>(new Value<T>(value));
}
int main()
{
std::vector<std::unique_ptr<Value<>>> values;
values.push_back(make_value(0));
values.push_back(make_value(0.0));
values.push_back(make_value(false));
}
Is it possible to have, let's say, a collection that is able to store
arbitrary pointers to Value instances?
No, it wouldn't work. However, there are at least to possibilities:
If you know beforehand every type you going to use in list, you can use boost::variant
You may make list of pointers to objects (actually void* or you may drop templates and make Value as base class) and somehow (e.g. dynamic_cast) cast them to some specific objects.
Mixins and function templates are two different ways of providing a behavior to a wide set of types, as long as these types meet some requirements.
For example, let's assume that I want to write some code that allows me to save an object to a file, as long as this object provides a toString member function (this is a rather silly example, but bear with me). A first solution is to write a function template like the following:
template <typename T>
void toFile(T const & obj, std::string const & filename)
{
std::ofstream file(filename);
file << obj.toString() << '\n';
}
...
SomeClass o1;
toFile(o1, "foo.txt");
SomeOtherType o2;
toFile(o2, "bar.txt");
Another solution is to use a mixin, using CRTP:
template <typename Derived>
struct ToFile
{
void toFile(std::string const & filename) const
{
Derived * that = static_cast<Derived const *>(this);
std::ofstream file(filename);
file << that->toString() << '\n';
}
};
struct SomeClass : public ToFile<SomeClass>
{
void toString() const {...}
};
...
SomeClass o1;
o.toFile("foo.txt");
SomeOtherType o2;
o2.toFile("bar.txt");
What are the pros and cons of these two approaches? Is there a favored one, and if so, why?
The first approach is much more flexible, as it can be made to work with any type that provides any way to be converted to a std::string (this can be achieved using traits-classes) without the need to modify that type. Your second approach would always require modification of a type in order to add functionality.
Pro function templates: the coupling is looser. You don't need to derive from anything to get the functionality in a new class; in your example, you only implement the toString method and that's it. You can even use a limited form of duck typing, since the type of toString isn't specified.
Pro mixins: nothing, strictly; your requirement is for something that works with unrelated classes and mixins cause them to be become related.
Edit: Alright, due to the way the C++ type system works, the mixin solution will strictly produce unrelated classes. I'd go with the template function solution, though.
I would like to propose an alternative, often forgotten because it is a mix of duck-typing and interfaces, and very few languages propose this feat (note: very close to Go's take to interfaces actually).
// 1. Ask for a free function to exist:
void toString(std::string& buffer, SomeClass const& sc);
// 2. Create an interface that exposes this function
class ToString {
public:
virtual ~ToString() {}
virtual void toString(std::string& buffer) const = 0;
}; // class ToString
// 3. Create an adapter class (bit of magic)
template <typename T>
class ToStringT final: public ToString {
public:
ToStringT(T const& t): t(t) {}
virtual void toString(std::string& buffer) const override {
toString(buffer, t);
}
private:
T t; // note: for reference you need a reference wrapper
// I won't delve into this right now, suffice to say
// it's feasible and only require one template overload
// of toString.
}; // class ToStringT
// 4. Create an adapter maker
template <typename T>
ToStringT<T> toString(T const& t) { return std::move(ToStringT<T>(t)); }
And now ? Enjoy!
void print(ToString const& ts); // aka: the most important const
int main() {
SomeClass sc;
print(toString(sc));
};
The two stages is a bit heavyweight, however it gives an astonishing degree of functionality:
No hard-wiring data / interface (thanks to duck-typing)
Low-coupling (thanks to abstract classes)
And also easy integration:
You can write an "adapter" for an already existing interface, and migrate from an OO code base to a more agile one
You can write an "interface" for an already existing set of overloads, and migrate from a Generic code base to a more clustered one
Apart from the amount of boiler-plate, it's really amazing how you seamlessly pick advantages from both worlds.
A few thoughts I had while writing this question:
Arguments in favor of template functions:
A function can be overloaded, so third-party and built-in types can be handled.
Arguments in favor of mixins:
Homogeneous syntax: the added behavior is invoked like any other member functions. However, it is well known that the interface of a C++ class includes not only its public member functions but also the free functions that operates on instances of this type, so this is just an aesthetic improvement.
By adding a non-template base class to the mixins, we obtain an interface (in the Java/C# sense) that can be use to handle all objects providing the behavior. For example, if we make ToFile<T> inherits from FileWritable (declaring a pure virtual toFile member function), we can have a collection of FileWritable without having to resort to complicated heterogeneous data structures.
Regarding usage, I'd say that function templates are more idiomatic in C++.
I want to be able to accept a Message& object which references either a Message1 or Message2 class. I want to be able to create a MessageWithData<Message1> or MessageWithData<Message2> based on the underlying type of the Message& object. For example, see below:
class Message {};
class Message1 : public Message {};
class Message2 : public Message {};
template<typename Message1or2>
class MessageWithData : public Message1or2 { public: int x, y; }
class Handler()
{
public:
void process(const Message& message, int x, int y)
{
// create object messageWithData whose type is
// either a MessageWithData<Message1> or a MessageWithData<Message2>
// based on message's type.. how do I do this?
//
messageWithData.dispatch(...)
}
};
The messageWithData class essentially contains methods inherited from Message which allow it to be dynamically double dispatched back to the handler based on its type. My best solution so far has been to keep the data separate from the message type, and pass it all the way through the dynamic dispatch chain, but I was hoping to come closer to the true idiom of dynamic double dispatch wherein the message type contains the variable data.
(The method I'm more or less following is from http://jogear.net/dynamic-double-dispatch-and-templates)
You're trying to mix runtime and compile-time concepts, namely (runtime-)polymorphism and templates. Sorry, but that is not possible.
Templates operate on types at compile time, also called static types. The static type of message is Message, while its dynamic type might be either Message1 or Message2. Templates don't know anything about dynamic types and they can't operate on them. Go with either runtime polymorphism or compile-time polymorphism, sometimes also called static polymorphism.
The runtime polymorphism approach is the visitor pattern, with double dispatch. Here is an example of compile-time polymorphism, using the CRTP idiom:
template<class TDerived>
class Message{};
class Message1 : public Message<Message1>{};
class Message2 : public Message<Message2>{};
template<class TMessage>
class MessageWithData : public TMessage { public: int x, y; };
class Handler{
public:
template<class TMessage>
void process(Message<TMessage> const& m, int x, int y){
MessageWithData<TMessage> mwd;
mwd.x = 42;
mwd.y = 1337;
}
};
You have
void process(const Message& message, int x, int y)
{
// HERE
messageWithData.dispatch(...)
}
At HERE, you want to create either a MessageWithData<Message1> or a MessageWithData<Message2>, depending on whether message is an instance of Message1 or Message1.
But you cannot do that, because the class template MessageWithData<T> needs to know at compile time what T should be, but that type is not available at that point in the code until runtime by dispatching into message.
As has been mentioned, it is not possible to build your template as is.
I do not see any issue with passing additional parameters, though I would perhaps pack them into a single structure, for ease of manipulation.
Certainly I find it more idiomatic to use a supplementary Data parameter, rather than extending a class hierarchy to shoehorn all this into a pattern.
It is an anti-pattern to try to make a design fit a pattern. The proper way is to adapt the pattern so that it fits the design.
That being said...
There are several alternatives to your solution. Inheritance seems weird, but without the whole design at hand it may be your best bet.
It has been mentioned already that you cannot freely mix compile-time and run-time polymorphisms. I usually use Shims to circumvent the issue:
class Message {};
template <typename T> class MessageShim<T>: public Message {};
class Message1: public MessageShim<Message1> {};
The scheme is simple and allow you to benefit from the best of both worlds:
Message being non-template mean that you can apply traditional OO strategies
MessageShim<T> being template mean that you can apply traditional Generic Programming strategies
Once done, you should be able to get what you want, for better or worse.
As Xeo says, you probably shouldn't do this in this particular case - better design alternatives exist. That said, you can do it with RTTI, but it's generally frowned upon because your process() becomes a centralised maintenance point that needs to be updated as new derived classes are added. That's easily overlooked and prone to run-time errors.
If you must persue this for some reason, then at least generalise the facility so a single function uses RTTI-based runtime type determination to invoke arbitrary behaviour, as in:
#include <iostream>
#include <stdexcept>
struct Base
{
virtual ~Base() { }
template <class Op>
void for_rt_type(Op& op);
};
struct Derived1 : Base
{
void f() { std::cout << "Derived1::f()\n"; }
};
struct Derived2 : Base
{
void f() { std::cout << "Derived2::f()\n"; }
};
template <class Op>
void Base::for_rt_type(Op& op)
{
if (Derived1* p = dynamic_cast<Derived1*>(this))
op(p);
else if (Derived2* p = dynamic_cast<Derived2*>(this))
op(p);
else
throw std::runtime_error("unmatched dynamic type");
}
struct Op
{
template <typename T>
void operator()(T* p)
{
p->f();
}
};
int main()
{
Derived1 d1;
Derived2 d2;
Base* p1 = &d1;
Base* p2 = &d2;
Op op;
p1->for_rt_type(op);
p2->for_rt_type(op);
}
In the code above, you can substitute your own Op and have the same runtime-to-compiletime handover take place. It may or may not help to think of this as a factory method in reverse :-}.
As discussed, for_rt_type has to be updated for each derived type: particularly painful if one team "owns" the base class and other teams write derived classes. As with a lot of slightly hacky things, it's more practical and maintainable in support of private implementation rather than as an API feature of a low-level enterprise library. Wanting to use this is still typically a sign of bad design elsewhere, but not always: occasionally there are algorithms (Ops) that benefit enormously:
compile-time optimisations, dead code removal etc.
derived types only need same semantics, but details can vary
e.g. Derived1::value_type is int, Derived2::value_type is double - allows algorithms for each to be efficient and use appropriate rounding etc.. Similarly for different container types where only a shared API is exercised.
you can use template metaprogramming, SFINAE etc. to customise the behaviours in a derived-type specific way
Personally, I think knowledge of and ability to apply this technique (however rarely) is an important part of mastering polymorphism.