#c #templates #dependency-injection #c 17
#c #шаблоны #внедрение зависимостей #c 17
Вопрос:
Как мне внедрить зависимость в класс, который я не могу описать без шаблона.
Проблема: я пишу библиотеку, в которой я хотел бы использовать зависимость от библиотеки продолжения от хост-приложения, известного как пользователи библиотеки, вместо того, чтобы включать что-то вроде pplx как часть моей библиотеки.
template<typename _ReturnType>
struct ITask
{
template<typename _ThenReturnType>
virtual std::shared_ptr<ITask<_ThenReturnType>> Then(const std::function<_ThenReturnType(_ReturnType)>amp;amp; func) = 0;
};
struct ITaskFactoryService
{
template<typename _TaskReturnType>
virtual std::shared_ptr<ITask<_TaskReturnType>> CreateTask(const std::function<_TaskReturnType()>) = 0;
};
struct IHostServices
{
virtual std::shared_ptr<ITaskFactoryService> TaskFactoryService() noexcept = 0;
};
Я знаю, что это невозможно в C . то, что я ищу, — это дизайнерское решение моей более серьезной проблемы. Я надеялся, что хост-приложение (потребитель библиотеки) сможет реализовать это и передать его в мою библиотеку. Я делаю это, потому что у разных хостов могут быть разные библиотеки продолжения.
Комментарии:
1. Я мог бы передать указатели void, я думаю, но мне кажется, что это очень грязно:(
2. Под библиотекой вы подразумеваете некоторые заголовки / исходные файлы — или действительно что-то вроде dll?
3. Вы не можете использовать virtual с шаблонами…
4. Не могли бы вы добавить еще немного информации о ваших потребностях?
5. Я не думаю, что ответ изменится, если это только заголовок, скомпилированный из источника, если это общая или статическая библиотека. Я думаю, что приведенный мной пример можно использовать как псевдокод, чтобы понять, чего я пытаюсь достичь. И да, тот факт, что я не могу использовать virtual с шаблонами, именно поэтому я пытаюсь найти наилучший способ внедрения зависимостей, точные типы которых заранее неизвестны.
Ответ №1:
Насколько я вас понял, вы хотите иметь возможность заменить свою библиотеку продолжения другой. Для этого вы хотели бы написать свои собственные оболочки вокруг них.
В общем, я думаю, вам следует написать больше «кода на C «. ITask и так далее выглядят более «Java-подобными» кодами.
Обычно вы определяете свойства / гарантии, которые требуются для, скажем так, «Task-Like-Types».
Например, тип, подобный задаче, должен иметь функцию-член «Then», которая принимает тип, подобный задаче, и возвращает новый тип, подобный задаче.
Чтобы проверить, является ли тип «похожим на задачу», вы обычно используете свойства типа, которые проверяют ваши требования. Альтернативный, более простой подход заключается в использовании «Type-Tags».
Здесь вы можете найти некоторый код на C , который показывает, как решить вашу проблему с дизайном:
#include <type_traits>
#include <iostream>
//type tags
namespace MyContinuationLib
{
class TaskLikeTypeTag{};
class NoType{};
}
//traits
namespace MyContinuationLib
{
//can be used for SFINAE
template<
typename T
>
class IsTaskLikeType
{
public:
static constexpr bool value = std::is_base_of_v<TaskLikeTypeTag, T>;
};
}
//task factory
namespace MyContinuationLib
{
template<
template<typename...> typename TaskImplTemplate
>
class TaskFactory
{
};
class HostServices;
template<
typename T
,typename UsedHostServices = HostServices
,typename PureT = std::remove_reference_t<T>
,typename = std::enable_if_t<
!MyContinuationLib::IsTaskLikeType<PureT>::value
>
,typename Dummy = void
>
auto ForceTaskLikeType(Tamp;amp; callable)
{
using TaskFactory = typename UsedHostServices::TaskFactory;
auto task = TaskFactory::CreateTask(callable);
return task;
}
template<
typename T
,typename PureT = std::remove_reference_t<T>
,typename = std::enable_if_t<
MyContinuationLib::IsTaskLikeType<PureT>::value
>
>
decltype(auto) ForceTaskLikeType(Tamp;amp; callable)
{
return callable;
}
}
//some TaskImplementation (you would need to define it's properties / what it should do!)
namespace MyContinuationLib
{
template<typename...>
class MyTask{};
template<
typename Callable
>
class MyTask<Callable> : public TaskLikeTypeTag
{
private:
Callable callable; //e.g. a lambda or a std::function
public:
constexpr MyTask(const Callableamp; c) : callable(c){}
public:
template<
typename CustomReturnType = NoType
,typename Invokable
, typename InvokeResult = std::invoke_result_t<Invokable>
, typename ReturnType = std::conditional_t<
std::is_same_v<CustomReturnType, NoType>,
InvokeResult,
CustomReturnType
>
,typename Result = MyTask<ReturnType, Invokable>
>
Result Then(Invokableamp;amp; func)
{
return Result(
[amp;]{
this->operator();
return static_cast<ReturnType>(func());
//what ever...
}
);
}
constexpr void SomeOtherTaskLikeFkt(){
callable();
std::cout << "SomeOtherTaskLikeFkt...";
}
constexpr decltype(auto) operator()(){
return callable();
}
};
template<>
class TaskFactory<MyTask>
{
public:
template<
typename Callable
>
constexpr static decltype(auto) CreateTask(Callableamp;amp; callable)
{
using PureCallable = std::remove_reference_t<Callable>;
return MyTask<PureCallable>{ callable };
}
};
}
//TaskFactories
namespace MyContinuationLib
{
class HostServices{
public:
//here we define what impl should be used!
using TaskFactory = MyContinuationLib::TaskFactory<MyTask>;
};
}
int main(){
using namespace MyContinuationLib;
auto task = ForceTaskLikeType([]{
std::cout << "Hello World!n";
});
task.SomeOtherTaskLikeFkt();
return 0;
}
Если вы не понимаете код или его части, пожалуйста, спросите.