Merge pull request #20039 from sivanov-work:gapi_empty_input

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
This commit is contained in:
Sergey Ivanov 2021-07-07 15:33:40 +03:00 committed by GitHub
parent ed2a698392
commit c0f63eb21f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 512 additions and 20 deletions

View File

@ -43,19 +43,6 @@ namespace detail
GOPAQUE, // a cv::GOpaqueU (note - exactly GOpaqueU, not GOpaque<T>!)
};
template<typename T>
constexpr const char* meta_to_string() noexcept;
template<>
constexpr const char* meta_to_string<cv::GMatDesc>() noexcept { return "GMatDesc"; }
template<>
constexpr const char* meta_to_string<cv::GScalarDesc>() noexcept { return "GScalarDesc"; }
template<>
constexpr const char* meta_to_string<cv::GArrayDesc>() noexcept { return "GArrayDesc"; }
template<>
constexpr const char* meta_to_string<cv::GOpaqueDesc>() noexcept { return "GOpaqueDesc"; }
template<>
constexpr const char* meta_to_string<cv::GFrameDesc>() noexcept { return "GFrameDesc";}
// Describe G-API types (G-types) with traits. Mostly used by
// cv::GArg to store meta information about types passed into
// operation arguments. Please note that cv::GComputation is

View File

@ -35,7 +35,6 @@ namespace detail
template<> struct ProtoToMeta<cv::GScalar> { using type = cv::GScalarDesc; };
template<typename U> struct ProtoToMeta<cv::GArray<U> > { using type = cv::GArrayDesc; };
template<typename U> struct ProtoToMeta<cv::GOpaque<U> > { using type = cv::GOpaqueDesc; };
template<> struct ProtoToMeta<cv::GFrame> { using type = cv::GFrameDesc; };
template<typename T> using ProtoToMetaT = typename ProtoToMeta<T>::type;
//workaround for MSVC 19.0 bug

View File

@ -117,6 +117,43 @@ namespace detail
static type get(std::tuple<Objs...>&& objs) { return std::forward<std::tuple<Objs...>>(objs); }
};
} // namespace detail
namespace util
{
template<typename ...L>
struct overload_lamba_set;
template<typename L1>
struct overload_lamba_set<L1> : public L1
{
overload_lamba_set(L1&& lambda) : L1(std::move(lambda)) {}
overload_lamba_set(const L1& lambda) : L1(lambda) {}
using L1::operator();
};
template<typename L1, typename ...L>
struct overload_lamba_set<L1, L...> : public L1, public overload_lamba_set<L...>
{
using base_type = overload_lamba_set<L...>;
overload_lamba_set(L1 &&lambda1, L&& ...lambdas):
L1(std::move(lambda1)),
base_type(std::forward<L>(lambdas)...) {}
overload_lamba_set(const L1 &lambda1, L&& ...lambdas):
L1(lambda1),
base_type(std::forward<L>(lambdas)...) {}
using L1::operator();
using base_type::operator();
};
template<typename... L>
overload_lamba_set<L...> overload_lambdas(L&& ...lambdas)
{
return overload_lamba_set<L...>(std::forward<L>(lambdas)...);
}
}
} // namespace cv
// \endcond

View File

@ -11,6 +11,7 @@
#include <array>
#include <type_traits>
#include <opencv2/gapi/util/compiler_hints.hpp>
#include <opencv2/gapi/util/throw.hpp>
#include <opencv2/gapi/util/util.hpp> // max_of_t
#include <opencv2/gapi/util/type_traits.hpp>
@ -44,6 +45,12 @@ namespace util
static const constexpr std::size_t value = detail::type_list_index_helper<0, Target, Types...>::value;
};
template<std::size_t Index, class... Types >
struct type_list_element
{
using type = typename std::tuple_element<Index, std::tuple<Types...> >::type;
};
class bad_variant_access: public std::exception
{
public:
@ -233,9 +240,87 @@ namespace util
template<typename T, typename... Types>
const T& get(const util::variant<Types...> &v);
template<std::size_t Index, typename... Types>
typename util::type_list_element<Index, Types...>::type& get(util::variant<Types...> &v);
template<std::size_t Index, typename... Types>
const typename util::type_list_element<Index, Types...>::type& get(const util::variant<Types...> &v);
template<typename T, typename... Types>
bool holds_alternative(const util::variant<Types...> &v) noexcept;
// Visitor
namespace detail
{
struct visitor_interface {};
// Class `visitor_return_type_deduction_helper`
// introduces solution for deduction `return_type` in `visit` function in common way
// for both Lambda and class Visitor and keep one interface invocation point: `visit` only
// his helper class is required to unify return_type deduction mechanism because
// 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 `visitor_return_type_deduction_helper` expose `operator()`
// uses only for class Visitor only for deduction `return type` in visit()
template<typename R>
struct visitor_return_type_deduction_helper
{
using return_type = R;
// to be used in Lambda return type deduction context only
template<typename T>
return_type operator() (T&&);
};
}
// Special purpose `static_visitor` can receive additional arguments
template<typename R, typename Impl>
struct static_visitor : public detail::visitor_interface,
public detail::visitor_return_type_deduction_helper<R> {
// assign responsibility for return type deduction to helper class
using return_type = typename detail::visitor_return_type_deduction_helper<R>::return_type;
using detail::visitor_return_type_deduction_helper<R>::operator();
friend Impl;
template<typename VariantValue, typename ...Args>
return_type operator() (std::size_t index, VariantValue&& value, Args&& ...args)
{
suppress_unused_warning(index);
return static_cast<Impl*>(this)-> visit(
std::forward<VariantValue>(value),
std::forward<Args>(args)...);
}
};
// Special purpose `static_indexed_visitor` can receive additional arguments
// And make forwarding current variant index as runtime function argument to its `Impl`
template<typename R, typename Impl>
struct static_indexed_visitor : public detail::visitor_interface,
public detail::visitor_return_type_deduction_helper<R> {
// assign responsibility for return type deduction to helper class
using return_type = typename detail::visitor_return_type_deduction_helper<R>::return_type;
using detail::visitor_return_type_deduction_helper<R>::operator();
friend Impl;
template<typename VariantValue, typename ...Args>
return_type operator() (std::size_t Index, VariantValue&& value, Args&& ...args)
{
return static_cast<Impl*>(this)-> visit(Index,
std::forward<VariantValue>(value),
std::forward<Args>(args)...);
}
};
template <class T>
struct variant_size;
template <class... Types>
struct variant_size<util::variant<Types...>>
: std::integral_constant<std::size_t, sizeof...(Types)> { };
// FIXME: T&&, const TT&& versions.
// Implementation //////////////////////////////////////////////////////////
@ -402,6 +487,22 @@ namespace util
throw_error(bad_variant_access());
}
template<std::size_t Index, typename... Types>
typename util::type_list_element<Index, Types...>::type& get(util::variant<Types...> &v)
{
using ReturnType = typename util::type_list_element<Index, Types...>::type;
return const_cast<ReturnType&>(get<Index, Types...>(static_cast<const util::variant<Types...> &>(v)));
}
template<std::size_t Index, typename... Types>
const typename util::type_list_element<Index, Types...>::type& get(const util::variant<Types...> &v)
{
static_assert(Index < sizeof...(Types),
"`Index` it out of bound of `util::variant` type list");
using ReturnType = typename util::type_list_element<Index, Types...>::type;
return get<ReturnType>(v);
}
template<typename T, typename... Types>
bool holds_alternative(const util::variant<Types...> &v) noexcept
{
@ -428,7 +529,130 @@ namespace util
{
return !(lhs == rhs);
}
} // namespace cv
namespace detail
{
// terminate recursion implementation for `non-void` ReturnType
template<typename ReturnType, std::size_t CurIndex, std::size_t ElemCount,
typename Visitor, typename Variant, typename... VisitorArgs>
ReturnType apply_visitor_impl(Visitor&&, Variant&,
std::true_type, std::false_type,
VisitorArgs&& ...)
{
return {};
}
// terminate recursion implementation for `void` ReturnType
template<typename ReturnType, std::size_t CurIndex, std::size_t ElemCount,
typename Visitor, typename Variant, typename... VisitorArgs>
void apply_visitor_impl(Visitor&&, Variant&,
std::true_type, std::true_type,
VisitorArgs&& ...)
{
}
// Intermediate resursion processor for Lambda Visitors
template<typename ReturnType, std::size_t CurIndex, std::size_t ElemCount,
typename Visitor, typename Variant, bool no_return_value, typename... VisitorArgs>
typename std::enable_if<!std::is_base_of<visitor_interface, typename std::decay<Visitor>::type>::value, ReturnType>::type
apply_visitor_impl(Visitor&& visitor, Variant&& v, std::false_type not_processed,
std::integral_constant<bool, no_return_value> should_no_return,
VisitorArgs&& ...args)
{
static_assert(std::is_same<ReturnType, decltype(visitor(get<CurIndex>(v)))>::value,
"Different `ReturnType`s detected! All `Visitor::visit` or `overload_lamba_set`"
" must return the same type");
suppress_unused_warning(not_processed);
if (v.index() == CurIndex)
{
return visitor.operator()(get<CurIndex>(v), std::forward<VisitorArgs>(args)... );
}
using is_variant_processed_t = std::integral_constant<bool, CurIndex + 1 >= ElemCount>;
return apply_visitor_impl<ReturnType, CurIndex +1, ElemCount>(
std::forward<Visitor>(visitor),
std::forward<Variant>(v),
is_variant_processed_t{},
should_no_return,
std::forward<VisitorArgs>(args)...);
}
//Visual Studio 2014 compilation fix: cast visitor to base class before invoke operator()
template<std::size_t CurIndex, typename ReturnType, typename Visitor, class Value, typename... VisitorArgs>
typename std::enable_if<std::is_base_of<static_visitor<ReturnType, typename std::decay<Visitor>::type>,
typename std::decay<Visitor>::type>::value, ReturnType>::type
invoke_class_visitor(Visitor& visitor, Value&& v, VisitorArgs&&...args)
{
return static_cast<static_visitor<ReturnType, typename std::decay<Visitor>::type>&>(visitor).operator() (CurIndex, std::forward<Value>(v), std::forward<VisitorArgs>(args)... );
}
//Visual Studio 2014 compilation fix: cast visitor to base class before invoke operator()
template<std::size_t CurIndex, typename ReturnType, typename Visitor, class Value, typename... VisitorArgs>
typename std::enable_if<std::is_base_of<static_indexed_visitor<ReturnType, typename std::decay<Visitor>::type>,
typename std::decay<Visitor>::type>::value, ReturnType>::type
invoke_class_visitor(Visitor& visitor, Value&& v, VisitorArgs&&...args)
{
return static_cast<static_indexed_visitor<ReturnType, typename std::decay<Visitor>::type>&>(visitor).operator() (CurIndex, std::forward<Value>(v), std::forward<VisitorArgs>(args)... );
}
// Intermediate recursion processor for special case `visitor_interface` derived Visitors
template<typename ReturnType, std::size_t CurIndex, std::size_t ElemCount,
typename Visitor, typename Variant, bool no_return_value, typename... VisitorArgs>
typename std::enable_if<std::is_base_of<visitor_interface, typename std::decay<Visitor>::type>::value, ReturnType>::type
apply_visitor_impl(Visitor&& visitor, Variant&& v, std::false_type not_processed,
std::integral_constant<bool, no_return_value> should_no_return,
VisitorArgs&& ...args)
{
static_assert(std::is_same<ReturnType, decltype(visitor(get<CurIndex>(v)))>::value,
"Different `ReturnType`s detected! All `Visitor::visit` or `overload_lamba_set`"
" must return the same type");
suppress_unused_warning(not_processed);
if (v.index() == CurIndex)
{
return invoke_class_visitor<CurIndex, ReturnType>(visitor, get<CurIndex>(v), std::forward<VisitorArgs>(args)... );
}
using is_variant_processed_t = std::integral_constant<bool, CurIndex + 1 >= ElemCount>;
return apply_visitor_impl<ReturnType, CurIndex +1, ElemCount>(
std::forward<Visitor>(visitor),
std::forward<Variant>(v),
is_variant_processed_t{},
should_no_return,
std::forward<VisitorArgs>(args)...);
}
} // namespace detail
template<typename Visitor, typename Variant, typename... VisitorArg>
auto visit(Visitor &visitor, const Variant& var, VisitorArg &&...args) -> decltype(visitor(get<0>(var)))
{
constexpr std::size_t varsize = util::variant_size<Variant>::value;
static_assert(varsize != 0, "utils::variant must contains one type at least ");
using is_variant_processed_t = std::false_type;
using ReturnType = decltype(visitor(get<0>(var)));
using return_t = std::is_same<ReturnType, void>;
return detail::apply_visitor_impl<ReturnType, 0, varsize, Visitor>(
std::forward<Visitor>(visitor),
var, is_variant_processed_t{},
return_t{},
std::forward<VisitorArg>(args)...);
}
template<typename Visitor, typename Variant>
auto visit(Visitor&& visitor, const Variant& var) -> decltype(visitor(get<0>(var)))
{
constexpr std::size_t varsize = util::variant_size<Variant>::value;
static_assert(varsize != 0, "utils::variant must contains one type at least ");
using is_variant_processed_t = std::false_type;
using ReturnType = decltype(visitor(get<0>(var)));
using return_t = std::is_same<ReturnType, void>;
return detail::apply_visitor_impl<ReturnType, 0, varsize, Visitor>(
std::forward<Visitor>(visitor),
var, is_variant_processed_t{},
return_t{});
}
} // namespace util
} // namespace cv
#endif // OPENCV_GAPI_UTIL_VARIANT_HPP

View File

@ -14,7 +14,6 @@
#include "api/gorigin.hpp"
#include "api/gproto_priv.hpp"
#include "logger.hpp"
// FIXME: it should be a visitor!
// FIXME: Reimplement with traits?
@ -277,13 +276,9 @@ void cv::validate_input_arg(const GRunArg& arg)
void cv::validate_input_args(const GRunArgs& args)
{
GAPI_LOG_DEBUG(nullptr, "Total count: " << args.size());
size_t index = 0;
for (const auto& arg : args)
{
GAPI_LOG_DEBUG(nullptr, "Process index: " << index);
validate_input_arg(arg);
index ++;
}
}

View File

@ -354,6 +354,20 @@ TEST(Variant, Get)
EXPECT_THROW(util::get<int>(cv2), util::bad_variant_access);
}
TEST(Variant, GetIndexed)
{
const TestVar cv(42);
// Test const& get()
EXPECT_EQ(42, util::get<0>(cv));
EXPECT_THROW(util::get<1>(cv), util::bad_variant_access);
// Test &get
TestVar cv2(std::string("42"));
EXPECT_EQ("42", util::get<1>(cv2));
EXPECT_THROW(util::get<0>(cv2), util::bad_variant_access);
}
TEST(Variant, GetWrite)
{
util::variant<int, std::string> v(42);
@ -486,4 +500,240 @@ TEST(Variant, EXT_IndexOf)
static_assert(6u == V::index_of<MyClass>(), "Index is incorrect");
}
namespace test_validation
{
struct MyType
{
friend std::ostream& operator<<(std::ostream& out, const MyType& src)
{
return out << "MyType"; (void) src;
}
};
class MyClass
{
friend std::ostream& operator<<(std::ostream& out, const MyClass& src)
{
return out << "MyClass"; (void) src;
}
};
struct MyBoolParamIndexedVisitor : cv::util::static_indexed_visitor<bool, MyBoolParamIndexedVisitor>
{
MyBoolParamIndexedVisitor(std::ostream &output) : out(output) {}
template<class Type>
bool visit(std::size_t index, Type val, int check)
{
bool result = false;
out << index << ":" << val <<",";
if(std::is_same<Type, int>::value)
{
result = !memcmp(&val, &check, sizeof(int));
}
return result;
}
std::ostream &out;
};
struct MyBoolNoParamNonIndexedVisitor : cv::util::static_indexed_visitor<bool, MyBoolNoParamNonIndexedVisitor>
{
MyBoolNoParamNonIndexedVisitor(std::ostream &output) : out(output) {}
template<class Type>
bool visit(std::size_t index, Type val)
{
out << index << ":" << val <<",";
return true;
}
std::ostream &out;
};
struct MyVoidNoParamNonIndexedVisitor : cv::util::static_visitor<void, MyVoidNoParamNonIndexedVisitor>
{
MyVoidNoParamNonIndexedVisitor(std::ostream &output) : out(output) {}
template<class Type>
void visit(Type val)
{
out << val << ",";
}
std::ostream &out;
};
struct MyVoidNoParamIndexedVisitor : cv::util::static_indexed_visitor<void, MyVoidNoParamIndexedVisitor>
{
MyVoidNoParamIndexedVisitor(std::ostream &output) : out(output) {}
template<class Type>
void visit(std::size_t Index, Type val)
{
out << Index << ":" << val <<",";
}
std::ostream &out;
};
}
TEST(Variant, DynamicVisitor)
{
using V = cv::util::variant<int, double, char, float, test_validation::MyType, test_validation::MyClass>;
V var{42};
{
std::stringstream ss;
test_validation::MyBoolParamIndexedVisitor visitor(ss);
EXPECT_TRUE(cv::util::visit(visitor, var, int{42}));
EXPECT_EQ(ss.str(), std::string("0:42,"));
}
std::stringstream ss;
test_validation::MyBoolNoParamNonIndexedVisitor visitor(ss);
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("0:42,"));
var = double{1.0};
EXPECT_TRUE(cv::util::visit(visitor, var));
EXPECT_EQ(ss.str(), std::string("0:42,1:1,"));
var = char{'a'};
EXPECT_TRUE(cv::util::visit(visitor, var));
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,"));
var = float{6.0};
EXPECT_TRUE(cv::util::visit(visitor, var));
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,"));
var = test_validation::MyType{};
EXPECT_TRUE(cv::util::visit(visitor, var));
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,4:MyType,"));
var = test_validation::MyClass{};
EXPECT_TRUE(cv::util::visit(visitor, var));
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,4:MyType,5:MyClass,"));
}
TEST(Variant, StaticVisitor)
{
using V = cv::util::variant<int, double, char, float, test_validation::MyType, test_validation::MyClass>;
V var{42};
std::stringstream ss;
test_validation::MyVoidNoParamNonIndexedVisitor visitor(ss);
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,"));
var = double{1.0};
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,1,"));
var = char{'a'};
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,1,a,"));
var = float{6.0};
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,1,a,6,"));
var = test_validation::MyType{};
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,1,a,6,MyType,"));
var = test_validation::MyClass{};
cv::util::visit(visitor, var);
EXPECT_EQ(ss.str(), std::string("42,1,a,6,MyType,MyClass,"));
}
TEST(Variant, StaticIndexedVisitor)
{
using V = cv::util::variant<int, double, char, float, test_validation::MyType, test_validation::MyClass>;
V var{42};
std::stringstream ss;
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor {ss}, var);
EXPECT_EQ(ss.str(), std::string("0:42,"));
var = double{1.0};
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor (ss), var);
EXPECT_EQ(ss.str(), std::string("0:42,1:1,"));
var = char{'a'};
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor (ss), var);
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,"));
var = float{6.0};
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor (ss), var);
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,"));
var = test_validation::MyType{};
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor (ss), var);
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,4:MyType,"));
var = test_validation::MyClass{};
cv::util::visit(test_validation::MyVoidNoParamIndexedVisitor (ss), var);
EXPECT_EQ(ss.str(), std::string("0:42,1:1,2:a,3:6,4:MyType,5:MyClass,"));
}
TEST(Variant, LambdaVisitor)
{
using V = cv::util::variant<int, double, char, float, test_validation::MyType, test_validation::MyClass>;
V var{42};
{
cv::util::visit(cv::util::overload_lambdas(
[](int value) {
EXPECT_EQ(value, 42);
},
[](double) {
ADD_FAILURE() << "can't be called for `double`";
},
[](char) {
ADD_FAILURE() << "can't be called for `char`";
},
[](float) {
ADD_FAILURE() << "can't be called for `float`";
},
[](test_validation::MyType) {
ADD_FAILURE() << "can't be called for `MyType`";
},
[](test_validation::MyClass) {
ADD_FAILURE() << "can't be called for `MyClass`";
},
[](std::string) {
ADD_FAILURE() << "can't be called for `std::string`, invalid type";
}
), var);
}
var = 'c';
{
cv::util::visit(cv::util::overload_lambdas(
[](int) {
ADD_FAILURE() << "can't be called for `int`";
},
[](double) {
ADD_FAILURE() << "can't be called for `double`";
},
[](char value) {
EXPECT_EQ(value, 'c');
},
[](float) {
ADD_FAILURE() << "can't be called for `float`";
},
[](test_validation::MyType) {
ADD_FAILURE() << "can't be called for `MyType`";
},
[](test_validation::MyClass) {
ADD_FAILURE() << "can't be called for `MyClass`";
},
[](std::string) {
ADD_FAILURE() << "can't be called for `std::string`, invalid type";
}
), var);
}
}
} // namespace opencv_test