New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check type of Any for casting #4426
Check type of Any for casting #4426
Conversation
@lisitsyn check this out |
src/shogun/lib/type_case.h
Outdated
{ | ||
if (type == sg_primitive_type<typename T1::Head>::value) | ||
{ | ||
typename T1::Head i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about a better name than i
Also the typenames could me more meaningful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! I just gave them the most standard names because I'm not very innovative with variable names..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should change the file name.. It's called type_case because the first implementation used a switch..
src/shogun/lib/type_case.h
Outdated
|
||
template <typename T1, typename T2> | ||
typename std::enable_if<std::is_same<T1, Types0>::value, int>::type | ||
recursive_type_finder(const Any& any, TYPE type, T2 func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about just type_finder? Or even something else?
src/shogun/lib/type_case.h
Outdated
} | ||
|
||
template <typename T> | ||
int type_finder(const Any& any, typemap& typesmap, T func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for_each_type? not appropriate anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that could work. Btw do you know why it is considered better to pass a lambda as a rvalue reference rather than as a copy, i.e. T&& func
rather than T func
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ehm ....it rings some bell, but I can't remember right now... stackoverflow ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be better to use rvalue reference if the lambda call is performed out of scope. But I don't see how that would happen, as the lambda never moves ownership. I cant tell if there is a downside to always use T&& func
.
src/shogun/lib/type_case.h
Outdated
{ | ||
}; | ||
|
||
#define SG_PRIMITIVE_TYPE(T, ptype) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure to udef all the macros after using them. We don't want to pollute the scope too much
src/shogun/lib/type_case.h
Outdated
// the head of the list returns the type | ||
typedef Types< | ||
bool, char, int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, | ||
uint64_t, float32_t, float64_t, floatmax_t>::type AllTypes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg_types
SG_TYPES
?
Also, those are only scalar types. What about adding a matrix type to start seeing what happens in those cases?
We will also need std::vector and friends.....which might show the issues with this approach: combinatorial explosions.
I guess we could argue that the number of types are limited.
src/shogun/lib/type_case.h
Outdated
// here we make types switchable | ||
enum class TYPE | ||
{ | ||
PT_BOOL = 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the PT_ prefix comes from somewhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that comes from shogun/tests/unit/base/SGObjectAll_unittest.cc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see, what about somehow re-using those from DataType.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah seem to be the same! But will have to address the containers..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well we don't want to support all the containers. Also the container definitions are part of the old type system and probably will be removed soon (nobody needs strings of SGVectors).
What is more likely is that we support matrix/vector of the ptypes, and then strings...but let's do that later when it is needed.
src/shogun/lib/type_case.h
Outdated
PT_FLOAT32 = 11, | ||
PT_FLOAT64 = 12, | ||
PT_FLOATMAX = 13, | ||
PT_SGOBJECT = 14, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure these are still supposed to be in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uuuh, actually we might need to add all base types here as well....sigh
@lisitsyn any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely a big step forward in dispatching any types.
I feel that we cannot support this direction for any type in shogun, as this would escalate the number of types. Instead, we want to define a carefully selected subset of types for which this feature could be used (e.g. all scalar types and histograms for tensorboard logging, generally all model parameters).
We can iteratively extend this after a first prototype works....
I agree, you need to be sensible about what types you want to check for. Otherwise you do end up spending more and more time looking for the type. Also all types take the same amount of time to be searched for, i.e. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
F
src/shogun/io/TBOutputFormat.cpp
Outdated
@@ -106,6 +107,13 @@ tensorflow::Event TBOutputFormat::convert_scalar( | |||
"Unsupported type %s", value.first.get_value().type_info().name()); | |||
} | |||
|
|||
auto write_summary = [&](auto type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question, could we explicit here with the variables passed on as reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean something like:
auto write_summary = [value, summaryValue](auto type) {summaryValue->set_simple_value(
any_cast<decltype(type)>(value.first.get_value()));
};
That should be safer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or would it be:
auto write_summary = [value, this](auto type) {this->summaryValue->set_simple_value(
any_cast<decltype(type)>(value.first.get_value()));
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like that yes, so that callers are forced to think about the scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw it seems like it should be like this in C++ 14 (including this
is C++11):
auto write_summary = [&summaryValue=summaryValue, &value](auto type) {
summaryValue->set_simple_value(
any_cast<decltype(type)>(value.first.get_value()));
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok great, this
is quite redundant anyways, I like the C++ updates :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, now just need to come up with a way to make sure people use this signature...
Would it be worth having some checks (not sure how)? Or just add somewhere in the documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine to let people look at examples and then use the same style. Documentation just outdates, and this is only for developers (who know how to browse the codebase) anyways.
What happens if a different signature is passed. Compile error I guess so that would be fine.
src/shogun/lib/type_case.h
Outdated
{ | ||
typename TypeList::Head temporary_type_holder; | ||
func(temporary_type_holder); | ||
return 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a specific reason for 0 and 1? As opposed to something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, could be a boolean, or could be a void function. This is just a way to capture errors, like in my example above we could do this:
if (!for_each_type(any, all_types, found_it_lambda)) {
std::cout << "FAILED" << std::endl;
}
Or could just throw an error from within the function if the type isn't found? Or allow a custom lambda to be executed in that situation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmmh, we could throw an exception (recently added customized exceptions might be worth checking this out). If not, a boolean might be better than the int
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question is whether there will be situations where no type is found but the caller doensnt know that....
If this does not make sense, exception would be best (with a nice error string) imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying out SG_ERROR, but it won't work here because it seems to only work with classes that inherit from CSObject.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vinx13 could you advise how to best use your new expections?
@gf712 try SG_SERROR for static contexts
Generally, we want to move towards better exceptions, with types. But this is especially true for errors that can be caused by users. This is more an internal error that is caused by a developer...so maybe the SG_SERROR is ok as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use SG_THROW
or SG_STHROW
to throw an exception with custom type.
The first argument is the type of the exception. The rest of the arguments are the same as SG_ERROR
e.g.
SG_THROW(ShogunException, "...")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah cool! Thanks, works as expected!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah actually no, this gives me a segmentation fault:
SG_SERROR("Unsupported type %s", any.type_info().name());
which I didn't have when I did this:
char buffer[256];
sprintf(buffer, "Unsupported type %s", any.type_info().name());
auto msg = std::string(buffer);
throw ShogunException(msg);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah nvm, I just need to add init_shogun() at the start of my example code!
src/shogun/lib/type_case.h
Outdated
ADD_TYPE_TO_MAP(float64_t , TYPE::PT_FLOAT64) | ||
ADD_TYPE_TO_MAP(floatmax_t , TYPE::PT_FLOATMAX) | ||
}; | ||
typemap non_integer_types = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice this will be usefil
src/shogun/io/TBOutputFormat.cpp
Outdated
any_cast<decltype(type)>(value.first.get_value())); | ||
}; | ||
|
||
for_each_type(value.first.get_value(), all_types, write_summary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry one more thing. Could we make the naming in a way that it is clear that this is a shogun utility function.
Like adding a prefix sg_
to all functions and global variances that have to do with this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! Btw, do you have any rule when to put or leave out functions/typedefs/enums from the namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimal evasion, so only make something as visible/complex as needed, not more
src/shogun/lib/type_case.h
Outdated
{ | ||
typedef Types< | ||
bool, char, int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, | ||
int64_t, uint64_t, float32_t, float64_t, floatmax_t>::type SG_TYPES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this collects all types that we support going from any to compile time type right?
So if we added SGMatrix<float64_t>, it would be added here or?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should be added there. And then in the enum class TYPE and use SG_PRIMITIVE_TYPE
to map the type to the enum and then to the appropriate map!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have one vector/matrix type for illustration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I tried it out. It can cause some issues though.
Let's say we add SGVector<float32_t>
to the sg_all_types
map. Everything works fine. But then if we use it in the TBOutputFormat.cpp we get a compile error, because the compiler tries to convert SGVector<float32_t>
to float
, because of the signature of summaryValue->set_simple_value(float)
(error: no viable conversion from 'shogun::SGVector<float>' to 'float'
).
We will need to keep a second TypeList with SGVector and probably another one for SGMatrix. And then either have different sg_for_each_*_type
functions, or come up with something fancier.. Working on it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this means the caller needs to know what container type she is dealing with ... mmh
Ok curious what you come up with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I think it is good to offer functions to deal with vector/matrix/std::vector separately... we do this specialization inside Any as well (see equals/clone)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok! I do think I found a solution. Can basically have different parameters in the sg_for_each_type to execute lambdas for specific datatypes, and then with some SFINAE magic can execute the right lambda or throw an error (or do nothing). Could a have a function signature like this:
template <typename Lambda1 = std::nullptr_t, typename Lambda2 = std::nullptr_t, typename Lambda3 = std::nullptr_t>
void sg_for_each_type(const Any& any, const typemap& typesmap,
Lambda1 scalar_func = nullptr, Lambda2 vector_func = nullptr, Lambda3 matrix_func = nullptr)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got this logic working. Basically can pass a lambda to be executed if it is a scalar, SGVector or SGMatrix. If the type that is found is any of those but there is no lambda defined for that type I can throw an error, warning or even nothing (but would be hard to debug).. This could be good if you expect Any
to have either of these types and can handle everything with a single call!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then if the type wasn't registered we get the ShogunException("Unsupported type %s") at runtime. Might be worth changing it to a static_assert
to get a compile time error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
++ for static_assert, always nicer
src/shogun/lib/type_case.h
Outdated
#define ADD_TYPE_TO_MAP(TYPENAME, TYPE_ENUM) \ | ||
{std::type_index(typeid(TYPENAME)), TYPE_ENUM}, | ||
|
||
typemap all_types = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg_ prefixes would be good I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean sg_all_types
right? (as opposed to the name of the macro)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
Just looking at the output. The error message is a bit cryptic. It should state "Type int32_t is not part of <float32_t, float64_t, float128_t>" or something in those lines |
Without much more code I can get the message |
That is fine actually, no neep to map |
src/shogun/lib/type_case.h
Outdated
@@ -75,10 +75,49 @@ namespace shogun | |||
{ | |||
typedef std::unordered_map<std::type_index, TYPE> typemap; | |||
|
|||
namespace type_internal | |||
{ | |||
std::string demangled_type(const char* name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, didn't we have a function like this somewhere already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it is similar, but it is a templated function that requires the type. Here we don't have the type, but have the std::type_info::name
instead (and we cant infer type from that), so I needed this custom function that I hid in this namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any way to re-use some redundant code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
especially the guard/macro bit....and only if it doesnt cause more trouble than it solves
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will give it a go! I can add both implementations in Any.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for example.
It might even go somewher else, as I dont think it is any specific
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put it in Any.cpp as I was getting a duplicate symbol linker error.
src/shogun/lib/type_case.h
Outdated
type_finder<SG_TYPES>(any, type, func); | ||
"Type %s is not part of %s", | ||
type_internal::demangled_type(any.type_info().name()).c_str(), | ||
type_internal::print_map(typesmap).c_str()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah sorry, we actually need it here, nevermind my above comment
src/shogun/lib/type_case.h
Outdated
} | ||
|
||
template <typename Lambda> | ||
void sg_for_each_type(const Any& any, typemap& typesmap, Lambda func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one thing: could you write a clear docstring for this function? So that it will be immediately clear to other devs how to use it (not so much how it works)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! Should I add information about the signature? I am assuming it will remain as it is now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes doxygen docstrings have a descr. for the function and then one field for every parameter
/** This is the description, describes the behaviour.
@param foo first parameter, and potential details
@param bar second parameter
@return the result
*/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I meant if I should write the signature for the lambda in the description, sorry!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think this is a good thing to document (we will see whether it outdates)
src/shogun/lib/type_case.h
Outdated
template <typename TypeList, typename Lambda> | ||
typename std::enable_if< | ||
(not std::is_same<TypeList, Types0>::value), void>::type | ||
sg_type_finder(const Any& any, TYPE type, Lambda func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this function used anywhere else, apart from sg_each_type? If not, then we should hide it from the global scope and only define/use it locally in the dispatcher
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I can move it to the other namespace I created? Btw, should the other namespace be nested in shogun (i.e. shogun::type_internal
), or completely separate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes move it to the other.
IDK whether they should be nested or not .... I guess so, would there be any case where we would use the latter on its own?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! No, I doubt it, it's very specific.
src/shogun/io/TBOutputFormat.cpp
Outdated
any_cast<decltype(type)>(value.first.get_value())); | ||
}; | ||
|
||
sg_for_each_type(value.first.get_value(), sg_all_types, write_summary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think now that this works nicely and looks good, we should also have some minimal unit testing that ensures very basic functionality of the for_each thing. One test that makes sure dispatching works, one that checks failure cases caused by ignorant devs, and maybe one for corner cases .?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All right! I am guessing those tests fit in Any_unittest.cc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, or an own test for the type_finger (whatever the name is)
To add tests, just add another cpp file and re-run cmake to detect it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is really getting there.
I made a few more comments but it gets quite marginal now ... so we should soon merge this
src/shogun/io/TBOutputFormat.cpp
Outdated
@@ -112,7 +112,7 @@ tensorflow::Event TBOutputFormat::convert_scalar( | |||
any_cast<decltype(type)>(value.first.get_value())); | |||
}; | |||
|
|||
sg_for_each_type(value.first.get_value(), sg_all_types, write_summary); | |||
sg_for_each_scalar_type(value.first.get_value(), sg_all_types, write_summary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmh this name is a bit of a monster now ;)
I am thinking loud ... sg_dispatch_any, sg_dispatch_any_vector, sg_dispatch_any_matrix, sg_dispatch_any_stl
Don't know if that is even better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Including "any" in the function name might make it easier to find the function in docs! So something along those lines would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ... I already wrote something earlier....
sg_any_dispatch is my fav atm ... but as said this might change
src/shogun/lib/type_case.h
Outdated
@@ -38,8 +42,10 @@ namespace shogun | |||
PT_FLOATMAX = 13, | |||
PT_SGOBJECT = 14, | |||
PT_COMPLEX128 = 15, | |||
PT_UNDEFINED = 16 | |||
PT_SGVECTOR_FLOAT32 = 16, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PT stands for "primitive type" in the old parameter framework. So keeping that in here doesnt make sense, maybe we need a new prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could just leave without a prefix? Otherwise just use SG_ instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about T_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, can use that
Getting this error from CI for the lint job:
Is that an issue with the script or is the format not correct in my code? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome that this now works for containers...
The only thing that concerns me is the API, I need to think a bit about how to avoid the default argument nullptr trick.
But it is good to see what happens if this is scaled up a bit.
bool, char, int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, | ||
int64_t, uint64_t, float32_t, float64_t, floatmax_t, SGVector<int32_t>, | ||
SGVector<int64_t>, SGVector<float32_t>, SGVector<float64_t>, | ||
SGVector<floatmax_t>, SGMatrix<int32_t>, SGMatrix<int64_t>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those should suffice for now, so nice :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need std::vector too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's think. The feature of going from any to typed will be used in
- logging (only model parameters, numerical stuff)
- modelselecton (again, mostly model parameters, here we will need
SGObject
as well ...) - not sure what else?
So with all that in mind, I think those types should be ok. And we can extend on request .... like done for any
src/shogun/lib/type_case.h
Outdated
@@ -38,8 +42,10 @@ namespace shogun | |||
PT_FLOATMAX = 13, | |||
PT_SGOBJECT = 14, | |||
PT_COMPLEX128 = 15, | |||
PT_UNDEFINED = 16 | |||
PT_SGVECTOR_FLOAT32 = 16, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about T_
src/shogun/lib/type_case.h
Outdated
|
||
enum class CONTAINER_TYPE | ||
{ | ||
SCALAR = 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CT_ ?
src/shogun/lib/type_case.h
Outdated
{ | ||
SCALAR = 1, | ||
VECTOR = 2, | ||
MATRIX = 3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if there is another "container type" enum, I don't really get why we then also need to state the containers here?
Having both seems redundant...? Maybe I just don't get it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this used anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, sorry, I was going to try something else and then gave up and forgot to delete this
src/shogun/lib/type_case.h
Outdated
SG_PRIMITIVE_TYPE(SGMatrix<float64_t>, TYPE::PT_SGMATRIX_FLOAT64) | ||
SG_PRIMITIVE_TYPE(SGMatrix<floatmax_t>, TYPE::PT_SGMATRIX_FLOATMAX) | ||
SG_PRIMITIVE_TYPE(SGMatrix<int32_t>, TYPE::PT_SGMATRIX_INT32) | ||
SG_PRIMITIVE_TYPE(SGMatrix<int64_t>, TYPE::PT_SGMATRIX_INT64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not nice and quite long....
BUT the good thing is that we (shogun framework devs) do this once, and not people who write higher level framework code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know.. The only other time that it might be used is if someone wants to create a custom map. Do you mean the list is long or the names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no just the big stack. But it is OK, as it is better than before
T value, ScalarLambdaT scalar_func, VectorLambdaT vector_func, | ||
MatrixLambdaT matrix_func) | ||
{ | ||
SG_SWARNING( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this should not be a runtime warning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have been thinking that a lot of these things should be static asserts to make debugging easier... If everything compiles any developer might become more complacent...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could check the signature of the function to see if it is void and has one parameter at compile time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got something like this working:
static_assert(std::is_same<
check_return_type<typename TypeList::Head, ScalarLambdaT, VectorLambdaT, MatrixLambdaT>,
ok>::value,
"All lambda definitions must be void and have the signature 'void f(auto type)'");
Not the most useful feature, but means we can write a test where the function signature is incorrect without compilation errors for the test! And gives the developer a "gentler" compile error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like such static assertions a lot!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, got 1 static assertion working! I didn't work on anything else yet as this took a while to figure out. I also wanted to add a test for static_assert with gtest, and that wasn't easy but now its working! :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I replace the warning with an exception? The idea of the warning was that there could be situations where no lambda implementation exists of the expected type on purpose and an exception could abort a ML run without need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, got 1 static assertion working! I didn't work on anything else yet as this took a while to figure out. I also wanted to add a test for static_assert with gtest, and that wasn't easy but now its working! :D
nice one!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I replace the warning with an exception? The idea of the warning was that there could be situations where no lambda implementation exists of the expected type on purpose and an exception could abort a ML run without need.
I would say yes, with an appropriate string describing what is wrong
src/shogun/io/TBOutputFormat.cpp
Outdated
any_cast<decltype(type)>(value.first.get_value())); | ||
}; | ||
|
||
sg_for_each_type(value.first.get_value(), sg_all_types, write_summary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since you have the vector thingi now in there, maybe also refactor the histogram bit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about that, but it uses std::vector instead of SGVector? Is there some implicit casting going on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmh phew ...
I would like to think it is possible to refactor this to SGVector?
On the other hand, it might be nice to support std:: structures as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the conversion to std::vector is necessary, because tensor board doesn't know about SGVector, like in this case:
shogun/src/shogun/io/TBOutputFormat.cpp
Lines 125 to 135 in 2f0b9cf
if (value.first.get_value().type_info().hash_code() == | |
typeid(std::vector<int8_t>).hash_code()) | |
{ | |
tensorflow::histogram::Histogram h; | |
tensorflow::HistogramProto* hp = new tensorflow::HistogramProto(); | |
auto v = any_cast<std::vector<int8_t>>(value.first.get_value()); | |
for (auto value_v : v) | |
h.Add(value_v); | |
h.EncodeToProto(hp, true); | |
summaryValue->set_allocated_histo(hp); | |
} |
src/shogun/io/TBOutputFormat.cpp
Outdated
any_cast<decltype(type)>(value.first.get_value())); | ||
}; | ||
|
||
sg_for_each_type(value.first.get_value(), sg_all_types, write_summary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you might kill me, but maybe "dispatch" should be part of the function name? sg_any_dispatch
comes to my mind now.... but not sure what is better. What are your toughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that sounds good! Was just waiting for a final decision before changing the name!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol ... let's hope this is the last :D
tests/unit/lib/type_case_unittest.cc
Outdated
TEST(Type_case, positional_lambdas) | ||
{ | ||
float32_t a_scalar = 42.0; | ||
auto a_vector = SGVector<float32_t>({a_scalar}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor
SGVector<float32_t> a_vector = {a_scalar}
is a tiny bit shorter ;)
tests/unit/lib/type_case_unittest.cc
Outdated
sg_for_each_type(any_vector, sg_all_types, nullptr, f_vector); | ||
EXPECT_EQ(counter, 2); | ||
|
||
sg_for_each_type(any_matrix, sg_all_types, nullptr, nullptr, f_matrix); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this API is not the most elegant....need to discuss this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm ok! Can have three different functions, it could make my life easier when specialising the helper function templates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder: Can't this be implicitly be done by forcing the lambda to have a different signature (including SGVector/matrix) ....
If not, I think having three functions is the best bet, at least API wise. They all could call the function you wrote here internally to avoid redundant code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could be possible, assuming that the compiler can implicitly convert SGVector and SGMatrix specialisations, right? Like in the summaryValue->set_simple_value(float value), I think all scalars can be interpreted as float right?
Btw seems like std::is_scalar
is quite a broad definition, will have to find a more strict definition like std::is_arithmetic
(potentially combined with a std::decay
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could be possible, assuming that the compiler can implicitly convert SGVector and SGMatrix specialisations, right?
What do you mean by that? BTW not sure if that helps, but It is definitely possible to extract inner types from templates, see this ugly thing for example
Like in the summaryValue->set_simple_value(float value), I think all scalars can be interpreted as float right?
That is true, but the compiler will not implicitly convertfloat
toSGVector
Btw seems like
std::is_scalar
is quite a broad definition, will have to find a more strict definition likestd::is_arithmetic
(potentially combined with astd::decay
).
Yes definitely, I would have a typedef for that that checks, see here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could be possible, assuming that the compiler can implicitly convert SGVector and SGMatrix specialisations, right?
What do you mean by that?
Basically if we have a lambda that takes SGVector<float32_t> it doesn't throw a compiler error when it sees the type SGVector<float64_t> and instead thinks that it can be converted.
I think @vigsterkr is working on CI stuff atm, so it might behave weirdly ... |
Added a static_assert as a first example, but could add some more to make sure any developer gets a nice message at compile time. This is largely based on https://channel9.msdn.com/Events/CPP/CppCon-2016/CppCon-2016-Roland-Bock-How-To-Test-staticassert and could probably cleaned up a bit more!
* changed sg_for_each_type to sg_any_dispatch * changed PT_ prefix to T_ * changed util function names
* this is not the final version of the PR but we can revert to here and have a stable API * added more decltype to enable multiple static assertions from more nested functions, i.e. check type of lambda param for 1 lambda API
* added same logic to SGVector and SGMatrix to return false if type hasn't been "registered" * renamed scalar to primitive
c552c45
to
a001a87
Compare
all we needed were variadic templates! No need for all the possible definitions like in gtest!
OK, should be good to go now! |
src/shogun/lib/any.h
Outdated
|
||
namespace shogun | ||
{ | ||
std::string demangled_type_helper(const char* name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we maybe hide this one from outside?
I somehow thought I merged this already? Shall I, or was something still missing? |
I think everything is in place and working! It's only a first iteration anyway. |
Ok shall I then? Just had another look and this is definitely something to use for a start |
Yes, and then when shogun supports c++17 we can clean this up with some |
Ok let’s watch ci. There might be compiler issues on some platforms with this modern stuff |
Awesome! |
OK! There seem to be some issues with msvc, like #4443.. But it might be an old bug in the compiler that has been fixed in more recent versions. |
CI seems to be failing during testing but due to |
That might be side effects from the changes. Keep in mind all tests are in one single executable and share memory so introducing memory errors in a test can make others fail, or adding a test could reveal existing memory problems due to layout changes |
OK, I'll investigate, but I can't reproduce these issues locally |
* list to keep track of typename * file adapted from gtest * added type_finder to check find Any type type_finder has a parameter to pass a lambda that is then called when the correct type is passed * added init_types to populate type identifiers when shogun is started * example of how type_finder could be used * fixed spacing with clang-format * moved to header only * this makes it compatible with existing code * maps are now defined statically * cleaned up code * removed header include from init * removed extern from map declaration as it is statically declared now * added undef for local macros * removed functions that are not required * changed names of function, typedefs and typenames changed these to more intuitive names * added more type maps * explicitly capture references in lambda * for_each_type is now a void function added SG_SERROR macro that throws a ShogunException * added sg_ prefix to utility functions and definitions * improved error messages * typenames can be interpreted more easily * code refactor * moved demangled_string with const char* parameter to any.cpp * moved helper functions to type_internal scope * added SGVector<float32> had to add two new functions, one for scalar and another one for vectors * sg_check_each_type accepts scalar, SGVector or SGMatrix * up to 3 lambdas can be passed for each type * added maps for containers * added some simple tests * improved typenames and fixed indentation * static_assert and test Added a static_assert as a first example, but could add some more to make sure any developer gets a nice message at compile time. This is largely based on https://channel9.msdn.com/Events/CPP/CppCon-2016/CppCon-2016-Roland-Bock-How-To-Test-staticassert and could probably cleaned up a bit more! * fixed formatting * minor refactor * changed sg_for_each_type to sg_any_dispatch * changed PT_ prefix to T_ * changed util function names * last round clean up of 3 lambda API * this is not the final version of the PR but we can revert to here and have a stable API * added more decltype to enable multiple static assertions from more nested functions, i.e. check type of lambda param for 1 lambda API * final cleanup * added is_sg_primitive to replace std::is_scalar * added same logic to SGVector and SGMatrix to return false if type hasn't been "registered" * renamed scalar to primitive * simplified Types struct all we needed were variadic templates! No need for all the possible definitions like in gtest! * dropped superfluous typename * hid demangled_type_helper in any_detail namespace * fixed bug when HAVE_CXA_DEMANGLE is not defined
* list to keep track of typename * file adapted from gtest * added type_finder to check find Any type type_finder has a parameter to pass a lambda that is then called when the correct type is passed * added init_types to populate type identifiers when shogun is started * example of how type_finder could be used * fixed spacing with clang-format * moved to header only * this makes it compatible with existing code * maps are now defined statically * cleaned up code * removed header include from init * removed extern from map declaration as it is statically declared now * added undef for local macros * removed functions that are not required * changed names of function, typedefs and typenames changed these to more intuitive names * added more type maps * explicitly capture references in lambda * for_each_type is now a void function added SG_SERROR macro that throws a ShogunException * added sg_ prefix to utility functions and definitions * improved error messages * typenames can be interpreted more easily * code refactor * moved demangled_string with const char* parameter to any.cpp * moved helper functions to type_internal scope * added SGVector<float32> had to add two new functions, one for scalar and another one for vectors * sg_check_each_type accepts scalar, SGVector or SGMatrix * up to 3 lambdas can be passed for each type * added maps for containers * added some simple tests * improved typenames and fixed indentation * static_assert and test Added a static_assert as a first example, but could add some more to make sure any developer gets a nice message at compile time. This is largely based on https://channel9.msdn.com/Events/CPP/CppCon-2016/CppCon-2016-Roland-Bock-How-To-Test-staticassert and could probably cleaned up a bit more! * fixed formatting * minor refactor * changed sg_for_each_type to sg_any_dispatch * changed PT_ prefix to T_ * changed util function names * last round clean up of 3 lambda API * this is not the final version of the PR but we can revert to here and have a stable API * added more decltype to enable multiple static assertions from more nested functions, i.e. check type of lambda param for 1 lambda API * final cleanup * added is_sg_primitive to replace std::is_scalar * added same logic to SGVector and SGMatrix to return false if type hasn't been "registered" * renamed scalar to primitive * simplified Types struct all we needed were variadic templates! No need for all the possible definitions like in gtest! * dropped superfluous typename * hid demangled_type_helper in any_detail namespace * fixed bug when HAVE_CXA_DEMANGLE is not defined
To cast Any back to its original type we need to check each type used in shogun before casting.
I implemented a function that does this check and then executes a lambda when it finds the right type.
It still needs some work:
int
? Theint
was just initially used for debugging.Example usage:
Expected output: