This is a follow-up to this post that I made yesterday, which perfectly answered my question, however I realized my problem is a little bit more complicated than what I asked in the first place.
Say, I have the following piece of code:
#include <iostream>
#include <typeinfo>
#include <ratio>
#include <vector>
#include <memory>
struct IStrategy
{
virtual void Declare() = 0;
virtual void Build() = 0;
};
template<typename T>
struct IStrategyStorer : public IStrategy
{
using StoredType=T;
T& stored;
IStrategyStorer(T& i_Input):
stored{i_Input}
{}
};
template<typename T>
struct BasicStrategy : public IStrategyStorer<T>
{
BasicStrategy(T& i_Input):
IStrategyStorer<T>(i_Input)
{
}
void Declare()
{
std::cout << "Declaring type : " << typeid(T).name() << " as parameter (Basic Strategy)" << std::endl;
IStrategyStorer<T>::stored = 0;
}
void Build()
{
std::cout << "Building type (Basic Strategy) : " << typeid(T).name() << std::endl;
IStrategyStorer<T>::stored = 1;
}
};
template<typename RATIO, typename T>
struct ImposedValueStrategy : public IStrategyStorer<T>
{
ImposedValueStrategy(T& i_Input):
IStrategyStorer<T>(i_Input)
{
}
void Declare()
{
std::cout << "Declaring type : " << typeid(T).name() << " as parameter (ImposedValue Strategy)" << std::endl;
IStrategyStorer<T>::stored = 0;
}
void Build()
{
std::cout << "Building type (ImposedValue Strategy): " << typeid(T).name() << std::endl;
IStrategyStorer<T>::stored = ((double) RATIO::num) / ((double) RATIO::den);
}
};
template<typename Ratio, typename PARENT_STRATEGY>
struct RatioStrategy : public PARENT_STRATEGY
{
using Type = typename PARENT_STRATEGY::StoredType;
RatioStrategy(Type& i_Input):
PARENT_STRATEGY(i_Input)
{
}
void Declare()
{
std::cout << "Transfering type declaration (currently in RatioStrategy)" << std::endl;
PARENT_STRATEGY::Declare();
}
void Build()
{
PARENT_STRATEGY::Build();
std::cout << "Applying ratio multiplication" << std::endl;
PARENT_STRATEGY::stored *= ((double)Ratio::num)/((double)Ratio::den);
}
};
template<typename Ratio, typename PARENT_STRATEGY>
struct SumStrategy : public PARENT_STRATEGY
{
using Type = typename PARENT_STRATEGY::StoredType;
SumStrategy(Type& i_Input):
PARENT_STRATEGY(i_Input)
{
}
void Declare()
{
std::cout << "Transfering type declaration (currently in SumStrategy)" << std::endl;
PARENT_STRATEGY::Declare();
}
void Build()
{
PARENT_STRATEGY::Build();
std::cout << "Applying ratio sum" << std::endl;
PARENT_STRATEGY::stored = ((double)Ratio::num)/((double)Ratio::den);
}
};
template<typename Ratio1, typename Ratio2, typename PARENT_STRATEGY>
struct AffineStrategy : public PARENT_STRATEGY
{
using Type = typename PARENT_STRATEGY::StoredType;
AffineStrategy(Type& i_Input):
PARENT_STRATEGY(i_Input)
{
}
void Declare()
{
std::cout << "Transfering type declaration (currently in AffineStrategy)" << std::endl;
PARENT_STRATEGY::Declare();
}
void Build()
{
PARENT_STRATEGY::Build();
std::cout << "Applying affine transformation" << std::endl;
PARENT_STRATEGY::stored = PARENT_STRATEGY::stored * ((double)Ratio1::num)/((double)Ratio1::den) ((double)Ratio2::num)/((double)Ratio2::den);
}
};
std::vector<IStrategy*> StrategiesList{}; // This stores the strategies for delayed building!
void buildStrategies()
{
for(auto& strat : StrategiesList)
{
strat -> Build();
}
}
template<template<typename> class Strategy = BasicStrategy, typename T>
void generic_parameter(T& i_Data)
{
Strategy<T>* strat = new Strategy<T>(i_Data);
StrategiesList.push_back((IStrategy*)strat);
strat->Declare();
}
template<class Strategy, typename T>
void generic_parameter(T& i_Data)
{
Strategy* strat = new Strategy(i_Data);
StrategiesList.push_back((IStrategy*)strat);
strat->Declare();
}
int main()
{
double d = 24;
generic_parameter(d);
generic_parameter<ImposedValueStrategy<std::ratio<4,3>, double>>(d);
generic_parameter<RatioStrategy<std::ratio<2,1>, SumStrategy<std::ratio<3,1>, BasicStrategy<double>>>>(d);
generic_parameter<AffineStrategy<std::ratio<2, 1>, std::ratio<5, 1>, BasicStrategy<double>>>(d);
buildStrategies();
std::cout << d << std::endl;
return 0;
}
The template template parameter used in the first overload of generic_parameter
lets me define a BasicStrategy
without having to specify the type twice (int
or double
, in this example).
However, I cannot find a way to use the same principles in order to deduce this type for non-default strategies, either with:
- Stacked strategies. For example: Basic strategy & multiply with a ratio
- More complex strategies that would require more template parameters than the type. For example: the ImposedValueStrategy
What I wish I would be able to write in the `main()~ function would be:
generic_parameter(d); // Uses BasicStrategy : currently works!
generic_parameter<ImposedValueStrategy<std::ratio<4,3>>>(d); // This does not compile
generic_parameter<AffineStrategy<std::ratio<2, 1>, std::ratio<5, 1>, BasicStrategy>>(d); // This does not compile
generic_parameter<RatioStrategy<std::ratio<2,1>, SumStrategy<std::ratio<3,1>, ImposedValueStrategy<std::ratio<3,1>>>>>(d); // This would not compile either
For context:
I am building a C 11 interface on top on a legacy API (written in C & Fortran) that enables the user to declare so called "parameters". The API then stores pointers to theses parameters, then later these variables will be filled with values by the API.
This works very well with arithmetic variables and C style arrays, but now requirements have changed and the C API must be able to declare any object as a parameter, hence every object requires a "strategy" which describes which part of the object must be declared to the API and how the object must be built, with the ability for the user to define his own "strategies".
Note that as the API fetches the data with a delay after the declaration, each strategy must hold a reference to the user data in order to build it when the data is ready. Also, it is a requirement to be able to "stack" strategies on top of each other as this avoids using temporary variables (most notably for unit conversions, which leads to a lot of mistakes).
CodePudding user response:
In your other question you already got to know about template template parameter. The missing ingredient is a little trick.
Suppose you have a class template
template <typename A,typename B>
struct foo {};
And now you want to bind the parameter A
and have B
deduced later. This would work if you have a template with a single parameter B
. So we can rewrite it to:
template <typename A>
struct wrapper {
template <typename B>
struct foo{};
};
Applying this and introducing template tempalte arguments in your code results in this (I removed some strategies and only kept some):
#include <iostream>
#include <typeinfo>
#include <ratio>
#include <vector>
#include <memory>
struct IStrategy {
virtual void Declare() = 0;
virtual void Build() = 0;
};
template<typename T> struct IStrategyStorer : public IStrategy {
using StoredType=T;
T& stored;
IStrategyStorer(T& i_Input) : stored{i_Input} {}
};
template<typename T> struct BasicStrategy : public IStrategyStorer<T> {
BasicStrategy(T& i_Input) : IStrategyStorer<T>(i_Input) { }
void Declare() {
std::cout << "Declaring type : " << typeid(T).name() << " as parameter (Basic Strategy)" << std::endl;
IStrategyStorer<T>::stored = 0;
}
void Build() {
std::cout << "Building type (Basic Strategy) : " << typeid(T).name() << std::endl;
IStrategyStorer<T>::stored = 1;
}
};
template <typename RATIO>
struct RatioLeaves {
template<typename T> struct ImposedValueStrategy : public IStrategyStorer<T> {
ImposedValueStrategy(T& i_Input) : IStrategyStorer<T>(i_Input) { }
void Declare() {
std::cout << "Declaring type : " << typeid(T).name() << " as parameter (ImposedValue Strategy)" << std::endl;
IStrategyStorer<T>::stored = 0;
}
void Build() {
std::cout << "Building type (ImposedValue Strategy): " << typeid(T).name() << std::endl;
IStrategyStorer<T>::stored = ((double) RATIO::num) / ((double) RATIO::den);
}
};
};
template <typename RATIO, template <typename> class PARENT_STRATEGY> struct RatioStrategies {
template <typename T> struct RatioStrategy : public PARENT_STRATEGY<T> {
using Type = typename PARENT_STRATEGY<T>::StoredType;
RatioStrategy(Type& i_Input) : PARENT_STRATEGY<T>(i_Input) {}
void Declare() {
std::cout << "Transfering type declaration (currently in RatioStrategy)" << std::endl;
PARENT_STRATEGY<T>::Declare();
}
void Build() {
PARENT_STRATEGY<T>::Build();
std::cout << "Applying ratio multiplication" << std::endl;
PARENT_STRATEGY<T>::stored *= ((double)RATIO::num)/((double)RATIO::den);
}
};
template <typename T> struct SumStrategy : public PARENT_STRATEGY<T> {
using Type = typename PARENT_STRATEGY<T>::StoredType;
SumStrategy(Type& i_Input): PARENT_STRATEGY<T>(i_Input) { }
void Declare(){
std::cout << "Transfering type declaration (currently in SumStrategy)" << std::endl;
PARENT_STRATEGY<T>::Declare();
}
void Build() {
PARENT_STRATEGY<T>::Build();
std::cout << "Applying ratio sum" << std::endl;
PARENT_STRATEGY<T>::stored = ((double)RATIO::num)/((double)RATIO::den);
}
};
};
std::vector<IStrategy*> StrategiesList{}; // This stores the strategies for delayed building!
void buildStrategies()
{
for(auto& strat : StrategiesList)
{
strat -> Build();
}
}
template<template<typename> class Strategy = BasicStrategy, typename T>
void generic_parameter(T& i_Data)
{
Strategy<T>* strat = new Strategy<T>(i_Data);
StrategiesList.push_back((IStrategy*)strat);
strat->Declare();
}
template<class Strategy, typename T>
void generic_parameter(T& i_Data)
{
Strategy* strat = new Strategy(i_Data);
StrategiesList.push_back((IStrategy*)strat);
strat->Declare();
}
int main()
{
double d = 24;
generic_parameter(d);
generic_parameter<RatioLeaves<std::ratio<4,3>>::ImposedValueStrategy>(d);
generic_parameter< RatioStrategies<std::ratio<2,1>,BasicStrategy>::SumStrategy>(d);
generic_parameter<RatioStrategies<std::ratio<2,1>,
RatioStrategies<std::ratio<3,1>,
RatioLeaves<std::ratio<3,1>>::ImposedValueStrategy
>::SumStrategy
>::RatioStrategy>(d);
buildStrategies();
std::cout << d << std::endl;
return 0;
}
The syntax is a little clunky, but I hope the general idea gets clear. Rather than supplying all arguments and instantiating the type, only a wrapper is instantiated and deduction of T
is kept for "later" when you call generic_parameter
.
Rather than the inheritance tree I would suggest to use variadic templates. Something along the line of
template <typename source, typename ... transformations>
struct value {
typename source::value_type get();
};
Where get
retrieves the value from the source and applies all transformations
. This would avoid the need for such deep nesting, while nesting could still be possible when desired.