CARVIEW |
Navigation Menu
-
-
Notifications
You must be signed in to change notification settings - Fork 56.2k
G-API: Implement variant visit() #20039
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
Conversation
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 start!
}; | ||
|
||
template<typename Visitor, typename Variant, typename... VisitorArg> | ||
typename Visitor::result_type visit(Visitor &visitor, //FIXME: Visitor && -> forbiddeby by Microsoft Visual Studio 2019 |
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.
forbidden by?
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 doesn't compile in that way: Visitor &&
- cannot deduct following arguments. I spent much time to workaround it and just put in as is. Maybe it MVS specific bug, because GCC version compiled fine
But, i' ve popped about some fix since i read your comment: it is possible to eliminate VisitorArgs
such Lambda can capture it and Class can capture it as member and it doesn't require to pass it as additional args. Moreover std::visit
doesn't receive any additional args
What do you think about it? After elimination VisitorArgs it will became possible to pass Visitor by universal reference and unblock passing instant lambda here
suppress_unused_warning(v); | ||
suppress_unused_warning(processed); | ||
return {}; | ||
} |
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's the purpose of this 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.
Oh I seem to get it - it just does nothing if a particular variant's type has been visited already?
If it is, then why should we have such an overload? We may possibly terminate the traversal on the match (haven't looked into the below code yet).
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.
May be this implementation is not pretty accurate - we can terminate recursion in another way... But in this PR I mostly focused on usability & applicability of visit instead of it's inner implementation
I hope we can use std variant soon or later :) and i doesn't concentrate on pretty templates implementation here...
But maybe i wrong and turning on c++14 & c++17 is very very far enough
if (!ret) | ||
{ | ||
out << "Expected: " << cv::detail::meta_to_string<Descr>() << ", got: "; | ||
meta_print_visitor v{out}; |
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.
hmm I see, its sort of prints type not the value -- actually, visitor is not required here at all? It is just out << cv::detail::meta_to_string<MetaT>()
(except the case for monostate
but that's workarountable)
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.
cv::detail::meta_to_string()
Do you mean the second visitor? it's required cause out
is variant too and we need expand it to print-out actual type we have (which actually in controversy with expected type), just for troubleshooting info in exception (user just read and change it)
const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) { | ||
switch (proto.index()) | ||
{ | ||
// FIXME: Auto-generate methods like this from traits: | ||
case GProtoArg::index_of<cv::GMat>(): | ||
case GProtoArg::index_of<cv::GMatP>(): | ||
return util::holds_alternative<cv::GMatDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GFrame>(): | ||
return util::holds_alternative<cv::GFrameDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GScalar>(): | ||
return util::holds_alternative<cv::GScalarDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GArrayU>(): | ||
return util::holds_alternative<cv::GArrayDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GOpaqueU>(): | ||
return util::holds_alternative<cv::GOpaqueDesc>(meta); | ||
|
||
default: | ||
GAPI_Assert(false); | ||
} | ||
return false; // should never happen | ||
}; |
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.
Amount of code removed here and added atop of that gives some hint... :)
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.
That's the controversial point of having a visitor as some class/etc. -> while previously we could use plain switch (well, not very automated but integrated perfectly into its environment), with a distinct class the handling logic is torn away from the context - we don't have scope closures for such objects like we could have with lambdas, for example.
Would it be possible to construct an ad-hoc visitor with typed lambdas right in place? It would be a game changer then.
I believe the limitation I've got into was that the visit
return type was determined by the visitor lambda called - and it could vary; but I believe we could relax such requirement here and 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.
That's the controversial point of having a visitor as some class/etc. -> while previously we could use plain switch
in current piece of code we need two stacked switch
s similar with
switch(expected)
{
case A:
...
switch (got)
{
case AA:
PRINT "A and AA mismatch"
case BB:
PRINT "A and BB mismatch"
}
case B:
switch (got)
case AA:
PRINT "B and AA mismatch"
}
In current implementation it solved by using two simple classes
I believe the limitation I've got into was that the visit return type was determined
exactly! it is the problem i faced ( without writting a lot of code for cast Lambda to std::fuction<> and extract return type
a) static_visitor as base is 'boost'-like way
b) lamba is std
But in case if we fix visitor for lambda usage, we cannot use lambda here, because lambda with auto
params is beginning with C++14, so code like that
auto l = []( const auto &v) {} //<---auto ?
It' won't work in c++11
So looks like class-style is only way for C++11
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.
P.S.
it is possible to use overloading in lambda like that
auto overloaded_lamda = make_overloaded_lambda([] (const GMatDescr& m) {},[] (const GOpaqDesc& o) {});
visit(overloaded_lambda, variant);
but it looks more ugly
}; | ||
|
||
template<typename Visitor, typename Variant, typename... VisitorArg> | ||
typename Visitor::result_type visit(Visitor &visitor, //FIXME: Visitor && -> forbiddeby by Microsoft Visual Studio 2019 |
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 doesn't compile in that way: Visitor &&
- cannot deduct following arguments. I spent much time to workaround it and just put in as is. Maybe it MVS specific bug, because GCC version compiled fine
But, i' ve popped about some fix since i read your comment: it is possible to eliminate VisitorArgs
such Lambda can capture it and Class can capture it as member and it doesn't require to pass it as additional args. Moreover std::visit
doesn't receive any additional args
What do you think about it? After elimination VisitorArgs it will became possible to pass Visitor by universal reference and unblock passing instant lambda here
suppress_unused_warning(v); | ||
suppress_unused_warning(processed); | ||
return {}; | ||
} |
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.
May be this implementation is not pretty accurate - we can terminate recursion in another way... But in this PR I mostly focused on usability & applicability of visit instead of it's inner implementation
I hope we can use std variant soon or later :) and i doesn't concentrate on pretty templates implementation here...
But maybe i wrong and turning on c++14 & c++17 is very very far enough
if (!ret) | ||
{ | ||
out << "Expected: " << cv::detail::meta_to_string<Descr>() << ", got: "; | ||
meta_print_visitor v{out}; |
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.
cv::detail::meta_to_string()
Do you mean the second visitor? it's required cause out
is variant too and we need expand it to print-out actual type we have (which actually in controversy with expected type), just for troubleshooting info in exception (user just read and change it)
const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) { | ||
switch (proto.index()) | ||
{ | ||
// FIXME: Auto-generate methods like this from traits: | ||
case GProtoArg::index_of<cv::GMat>(): | ||
case GProtoArg::index_of<cv::GMatP>(): | ||
return util::holds_alternative<cv::GMatDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GFrame>(): | ||
return util::holds_alternative<cv::GFrameDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GScalar>(): | ||
return util::holds_alternative<cv::GScalarDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GArrayU>(): | ||
return util::holds_alternative<cv::GArrayDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GOpaqueU>(): | ||
return util::holds_alternative<cv::GOpaqueDesc>(meta); | ||
|
||
default: | ||
GAPI_Assert(false); | ||
} | ||
return false; // should never happen | ||
}; |
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.
That's the controversial point of having a visitor as some class/etc. -> while previously we could use plain switch
in current piece of code we need two stacked switch
s similar with
switch(expected)
{
case A:
...
switch (got)
{
case AA:
PRINT "A and AA mismatch"
case BB:
PRINT "A and BB mismatch"
}
case B:
switch (got)
case AA:
PRINT "B and AA mismatch"
}
In current implementation it solved by using two simple classes
I believe the limitation I've got into was that the visit return type was determined
exactly! it is the problem i faced ( without writting a lot of code for cast Lambda to std::fuction<> and extract return type
a) static_visitor as base is 'boost'-like way
b) lamba is std
But in case if we fix visitor for lambda usage, we cannot use lambda here, because lambda with auto
params is beginning with C++14, so code like that
auto l = []( const auto &v) {} //<---auto ?
It' won't work in c++11
So looks like class-style is only way for C++11
const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) { | ||
switch (proto.index()) | ||
{ | ||
// FIXME: Auto-generate methods like this from traits: | ||
case GProtoArg::index_of<cv::GMat>(): | ||
case GProtoArg::index_of<cv::GMatP>(): | ||
return util::holds_alternative<cv::GMatDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GFrame>(): | ||
return util::holds_alternative<cv::GFrameDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::GScalar>(): | ||
return util::holds_alternative<cv::GScalarDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GArrayU>(): | ||
return util::holds_alternative<cv::GArrayDesc>(meta); | ||
|
||
case GProtoArg::index_of<cv::detail::GOpaqueU>(): | ||
return util::holds_alternative<cv::GOpaqueDesc>(meta); | ||
|
||
default: | ||
GAPI_Assert(false); | ||
} | ||
return false; // should never happen | ||
}; |
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.
P.S.
it is possible to use overloading in lambda like that
auto overloaded_lamda = make_overloaded_lambda([] (const GMatDescr& m) {},[] (const GOpaqDesc& o) {});
visit(overloaded_lambda, variant);
but it looks more ugly
return true; | ||
} | ||
std::ostream &out; | ||
}; |
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 outside of test scope because c++ told me local classes with templates are forbidden
for special (non-templated) methods it should compile fine
@alalek |
7b375b9
to
101f068
Compare
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.
LGTM 👍
} | ||
}; | ||
|
||
struct MyParamDynamicVisitor : cv::util::static_indexed_visitor<bool, MyParamDynamicVisitor> |
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 bool
mandatory? can't it be inferred via CRTP?
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 the static_indexed_visitor
is already using CRTP
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.
- From my perspective it breaks erroneous-prone criteria:
It is possible writing by mistake
class MyClass : public static_visitor<MyClass> {
bool operator()(int)
{/*1000 lines of code*/}
void operator()(float)
{/*1000 lines of code*/}
int operator() (char)
}
but with explicit type it less possible
AND the more strongest reason
- to deduct return_type in CRTP you must use the same construction as in visit()
decltype(visit_impl(get<0>(var)))
wherevisit_impl
is upper-level hierarchy wrapper class. But
decltype(visit(get<0>(var)))
<---- where can i get var
variable here?
Look
somewhere in CRTP...
template<class T>
auto operator() (size_t index, const T& val) -> decltype(Impl::visit(declval(T))) <---!
declval(T) - is useless here, because it's declaration and doesn't define nothing because nothing to instantiate
EXPECT_EQ(value, 42); | ||
}, | ||
[](double) { | ||
EXPECT_TRUE(false); |
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 believe ADD_FAILURE()
or FAIL
is the right thing in this case.
Also, should this list cover all Types..
range? What if we want to handle only this or that case? (e.g. T1
and T3
only)? Can a "default" lambda (with no value passed in, or with variant itself passed in) be introduced 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.
NO, it's limitation for C++11 lambda. In C++14 generic lambda can helps to reduce amount of code
That's because I insist to keep ClassVariant implementation too
Can a "default" lambda
It is possible to implement Bypass
struct here
namespace opencv {
namespace utils {
template<class T>
class Bypass {
operator(T val) {} <---nothing to do
};
}
}
temlplate<class T...> overload_lambdas_set<Bypass<T>...> make_bypass() {...}
Then
cv::util::visit(cv::overload_lambdas([] (int) {}, make_bypass<double,... ???,...>(), ...);
But I'm not sure about usefulness of this case. maybe as theoretic exploration pros-cons
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.
My comment was not about the template lambdas but about not specifying some overloads
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 won't compile, i believe
Also i believe that it is not extendable, error proneous and dangerous: Because after planned extension of variant types list ( adding new type for example) you will not receive any error and newly added type would not be processed silently
}; | ||
|
||
|
||
struct MyNoParamStaticVisitor : cv::util::static_visitor<void, MyNoParamStaticVisitor> |
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, what's the difference between Static and Dynamic visitors 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.
This class implements visit(Type val)
, but Dynamic implements visit(std::size_t index, Type val
I agree that class name is confused, i will rename 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.
What's the difference between those and should user care about that?
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 second version visit(std::size_t index, Type val)
for indexed_visitor
returns not only value from variant but also its position index and provide additional flexibility in processing the value ( for example logging, printing, or more compilex use case in serialization maybe)
public detail::visitor_return_type_deduction_helper<R> { | ||
|
||
using return_type = typename detail::visitor_return_type_deduction_helper<R>::return_type; | ||
using detail::visitor_return_type_deduction_helper<R>::operator(); |
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's the idea behind that?
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 getting return type in common case: Lambda + Class Visitor - and keep one interface invocation point: visit() only - it requires to unify return_type
deduction mechanism. For Lambda
it is possible to take type of decltype(visitor(get<0>(var)))
but for Class Visitor
there is no operator()
in base case, because it provides operator() (std::size_t index, ...)
So operator()
uses only for Class Visitor
only for deduction return type in visit()
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 it, but a comment in the code would help here 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.
sure, i'll add it
std::array<bool, non_variadic_args_num + sizeof...(VisitorArgs)> dummy{ | ||
(suppress_unused_warning(visitor), true), | ||
(suppress_unused_warning(v), true), | ||
(suppress_unused_warning(processed), true), | ||
(suppress_unused_warning(no_return), true), | ||
(suppress_unused_warning(args),true)...}; |
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 don't quite get what it does - why do we need a construct like this? Does it have any side effects? I believe no?
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.
just stack allocation of several booleans in the end of recursion.
Used cause several compilers notifies it as unused variable
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 whole construct is solely about the unused 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 believe we can drop names from the method signature then and that's 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.
good idea, thanks. i'll try 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.
Well done!
public detail::visitor_return_type_deduction_helper<R> { | ||
|
||
using return_type = typename detail::visitor_return_type_deduction_helper<R>::return_type; | ||
using detail::visitor_return_type_deduction_helper<R>::operator(); |
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 it, but a comment in the code would help here a lot.
fee25ef
to
3160171
Compare
@alalek |
G-API: Implement variant visit() * Add variant visitor, use visitor for check compile args * Fix GAPI UT: variant *compiler * Aling apply_visior with std, fix indentations * Fix compilation (included compiler_hints.hpp) * Fix compilation (due gapi standalone) * Fix compilation2 (Docs) * Add Lambdas overload, Refactor visit() * Add ReturnType auto deduction * Fix comilation * Fix compilation * Fix warnings * Try to fix MSVC14 * Fix docs * Try fix Win compile * Fix Docs again * Revert GAPI empty input fix * Apply comment for `tuple_element` * Add std::decay for std::base_of to work arounf armv7 problem * Apply review comments * Apply review comments: added comment & removed unused args * Fix docs compilation
Implement
visitor
forvariant
and few helpers:type_list_element
for gettingtype
based onindex
(similarly withstd::get<>
)variant_size
for getting holdentypes
count (similarly withstd::variant_size<>
)overload_set
for Lambdas to enable lambda operator() overloadingvisit
for applying function on variant type (similarly withstd::visit
) use two approaches:a) Specific visitor type must inherits either adapter
static_visitor
mix-in orstatic_indexed_visitor
and implementvisit
method which receives: variant param and extra data passed tovisit
or additional index of variant param passed too for descendant ofstatic_indexed_visitor
.b) uses
overload_set
of lambdas as in-place visitorAdded UTs:
variant
get functionalityvisit
with usingstatic_visitor
interface with additional params and no paramsvisit
with usingstatic_indexed_visitor
interface with additional params and no paramsvisit
with overloaded Lambdas setPull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
Patch to opencv_extra has the same branch name.