I have a class and need to validate that it's function calls are being called w/ the right parameters. The function signature is always the same (sans 1 argument type). So, naturally I went for a templated approach. So generally the validation policy would have a template parameter per data type it could handle:
using P = Policy<int, double, UserDefined>
Or something of that ilk.
I got it to compile, but the caveat is that if double
and int
(or anything a double
can convert to actually) are both template parameters, the double
will be implicitly converted.
The policy looks like this:
template <typename... T>
class BasicValidationPolicy { };
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
};
template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
using SetHandler = std::function<void(int, T)>;
void RegisterSetHandler(const SetHandler& handler)
{
m_setHandler = handler;
}
void Set(int n, const T& val) {
if (m_setHandler) {
m_setHandler(n, val);
}
}
private:
SetHandler m_setHandler{nullptr};
};
The class that uses it...
template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy {
public:
void OnSetInt(int n, int64_t v)
{
ValidatorPolicy::Set(n, v);
}
void OnSetDouble(int n, double d)
{
ValidatorPolicy::Set(n, d);
}
};
Usage:
int main()
{
using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
MyClass<Policy> m;
m.Policy::RegisterSetHandler([](int i, double value) {
// by this point value is an int64_t
std::cout << "Got double " << i << ", " << value << "\n";
});
double d{35.2135};
m.OnSetDouble(1, d);
}
To boot, doing this does work
using Policy = BasicValidationPolicy<double, int64_t>;
So I guess I'm missing something about the template deduction. Looks like it tries to match double
against std::int64_t
says "meh, good enough", and moves on. Nice to know a way around it (kind of) but that looks like it would be very tricky to maintain.
It's complicated...
First of all: you have a recursive template class, BasicValidationPolicy
, where you define two methods and you want that all methods, for all recursion steps of the class, are available.
Unfortunately, the definition of the methods in the derived classes hide the method in base classes.
To un-hide the inherited methods, you have to explicitly add a pair of using
using BasicValidationPolicy<Rest...>::Set;
using BasicValidationPolicy<Rest...>::RegisterSetHandler;
At this point, the code doesn't compile because you need a Set()
and a RegisterSetHandler()
in the ground case class. You have declared a dummy RegisterSetHandler()
but not a dummy Set()
. You have to add one, so the ground case become
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
void Set();
};
Now your MyClass<Policy>
object expose two RegisterSetHandler()
methods (before only one): one receiving a std::function<void(int, std::int64_t)>
, the other (before hidden) receiving a std::function<void(int, double)>
.
But when you pass a lambda, you have a chicken-and-egg problem: the lambda can be converted to a std::function
but isn't a std::function
. So can't be used to deduce the template parameters of std::function
because the types are to be known before to deduce them.
A possible solution is impose a lambda/std::function
conversion in the call
// ..........................VVVVVVVVVVVVVV
m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
// by this point value is an int64_t
std::cout << "Got double " << i << ", " << value << "\n";
}});
// ...........................^
using also the template deduction guides introduced in C++17.
So your code become
#include <iostream>
#include <functional>
template <typename... T>
class BasicValidationPolicy { };
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
void Set();
};
template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
using SetHandler = std::function<void(int, T)>;
using BasicValidationPolicy<Rest...>::Set;
using BasicValidationPolicy<Rest...>::RegisterSetHandler;
void RegisterSetHandler(const SetHandler& handler)
{
m_setHandler = handler;
}
void Set(int n, const T& val) {
if (m_setHandler) {
m_setHandler(n, val);
}
}
private:
SetHandler m_setHandler{nullptr};
};
template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy {
public:
void OnSetInt(int n, int64_t v)
{
ValidatorPolicy::Set(n, v);
}
void OnSetDouble(int n, double d)
{
ValidatorPolicy::Set(n, d);
}
};
int main ()
{
using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
MyClass<Policy> m;
m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
std::cout << "Got double " << i << ", " << value << "\n";
}});
double d{35.2135};
m.OnSetDouble(1, d);
}