I want to create a function template that creates a list of all legal/valid instances of some class. The class itself is somehow informed about the values that each of its members can take. Function template:
template <typename T>
std::list<T> PossibleInstantiations();
Now, if SomeClass somehow contains information about legal instantiations of all its members (in below example, legal instantiations of i are 1,4,5 and legal instantiations of j are 1.0, 4.5), then
PossibleInstantiations<SomeClass>();
should yield a list containing elements {SomeClass(1,1.0), SomeClass(1,4.5), SomeClass(4,1.0), SomeClass(4,4.5), SomeClass(5,1.0), SomeClass(5,4.5)}.
Of course, adding extra elements (+ associated valid values) should automatically be handled by PossibleInstantiations.
Someclass would be implemented something like below. In what way should the plumbing be added to client classes (e.g. MyClass), and how should PossibleInstantiations be implemented?
class SomeClass
{
public:
int i;
static std::list<int> ValidValuesFori();
double j;
static std::list<double> ValidValuesForj();
SomeClass(int i, double j);
//int k;
//static std::list<int> ValidValuesFork(); //could be implemented at some later stage.
//Which would mean the constructor becomes:
//SomeClass(int i, int j, int k)
//...
//Extra wiring for pointing out that i and ValidValuesFori belong to each other,
//and perhaps for pointing out that i is the first element in the constructor, or so?
//..
};
static std::list<int> SomeClass::ValidValuesFori()
{
return std::list<int>{1, 4, 5};
//Other options:
//std::list<int> ValidValues;
//for (int i = 0; i < 1000; i++)
//{
// if (i % 3 == 0)
// ValidValues.push_back(i);
//}
//return ValidValues;
}
static std::list<double> SomeClass::ValidValuesForj()
{
return std::list<double>{1.0, 4.5};
}
SomeClass::SomeClass(int i, double j)//or other constructor
:i{ i }, j{ j } {}
Why make it hard if you can make it easy? You already say that SomeClass should know which values are allowed for its members. You could make this explicit by moving GetPossibleImplementations() to the class:
class SomeClass
{
public:
static std::vector<SomeClass> GetPossibleImplementations()
{
std::vector<SomeClass> values;
for (int i : ValidValuesFori())
for (double j : ValidValuesForj())
values.push_back(SomeClass(i, j));
return values;
}
// other methods
// [...]
}
Then you can still add the template function, if you need it:
template <typename T>
std::vector<T> GetPossibleImplementations()
{
return T::GetPossibleImplementations();
}
Moving the logic into the class has the following advantages:
i conflicts with some value of j?There are situations where you cannot change the implementation of SomeClass and you need an external solution. Even in that case, I think you should keep the logic class-specific: Declare the generic function GetPossibleImplementations<T>() as above, but implement it only for the specific classes:
template <typename T>
std::vector<T> GetPossibleImplementations();
template <>
std::vector<SomeClass> GetPossibleImplementations<SomeClass>()
{
std::vector<SomeClass> values;
for (int i : SomeClass::ValidValuesFori())
for (double j : SomeClass::ValidValuesForj())
values.push_back(SomeClass(i, j));
return values;
}
The main differences between the two versions is that the first version yields a compile error if the template argument T does not support T::GetPossibleImplementations() and the second version yields a link error if GetPossibleImplementations<T> is not implemented.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With