Is it possible to create a method that accepts a concrete implementation of a templated interface?
To illustrate the problem I created a minimal example. See produce(ISource<IProduct>* source)
below:
// Product
class IProduct {
public:
virtual ~IProduct() = default;
};
class Bread : public IProduct {};
class Cucumber : public IProduct {};
// Source
template<typename T>
class ISource {
public:
virtual T* get() = 0;
};
class Bakery : public ISource<Bread> {
public:
Bread* get() override {
return new Bread();
}
};
class Grocery : public ISource<Cucumber> {
public:
Cucumber* get() override {
return new Cucumber();
}
};
// Producer
class IProducer {
public:
virtual IProduct* produce(ISource<IProduct>* source) = 0;
};
class Shop : public IProducer {
public:
IProduct* produce(ISource<IProduct>* source) override {
return source->get();
}
};
My attempts:
template<typename T>
T* test(IProducer* producer, ISource<T>* source) {
// (1) Cannot initialize a parameter of type 'ISource<IProduct> *' with an lvalue of type 'ISource<Bread> *'
T* product = dynamic_cast<T *>(producer->produce(source));
return product;
}
int main(int argc, char** argv) {
auto bakery = new Bakery();
auto shop = new Shop();
// FAILS: see (1)
Bread* bread1 = test(shop, bakery);
}
ISource<IProduct>
and ISource<Bread>
are two completely separate and unique types, and cannot be cast to one another, and you cannot pass one where the other is expected. (unless you define a conversion yourself).
you can however erase their different types using type-erasure, and only keep the fact that they can both produce IProduct*
the C++ way to do this using the standard library is using std::function
#include <functional>
// Product
class IProduct {
public:
virtual ~IProduct() = default;
};
class Bread : public IProduct {};
// Source
template<typename T>
class ISource {
public:
virtual T* get() = 0;
};
class Bakery : public ISource<Bread> {
public:
Bread* get() override {
return new Bread();
}
};
// Producer
class IProducer {
public:
virtual IProduct* produce(std::function<IProduct*()> source) = 0;
};
class Shop : public IProducer {
public:
IProduct* produce(std::function<IProduct*()> source) override {
return source();
}
};
template<typename T>
T* test(IProducer* producer, ISource<T>* source) {
T* product = dynamic_cast<T*>(producer->produce([&]()->IProduct* {return source->get(); }));
return product;
}
int main(int argc, char** argv) {
auto bakery = new Bakery();
auto shop = new Shop();
Bread* bread1 = test(shop, bakery);
}
type-erasure aside, you can have ISource
not be templated and use covariant return types.
class ISource {
public:
virtual IProduct* get() = 0;
};
class Bakery : public ISource {
public:
Bread* get() override {
return new Bread();
}
};
class Grocery : public ISource {
public:
Cucumber* get() override {
return new Cucumber();
}
};
// Producer
class IProducer {
public:
virtual IProduct* produce(ISource* source) = 0;
};
class Shop : public IProducer {
public:
IProduct* produce(ISource* source) override {
return source->get();
}
};
template<typename source_t>
auto test(IProducer* producer, source_t* source) {
auto product = dynamic_cast<decltype(source->get())>(producer->produce(source));
return product;
}