使用偏函数应用程序或 curry 与重载和 std::visit 结合使用时理解错误
Posted
技术标签:
【中文标题】使用偏函数应用程序或 curry 与重载和 std::visit 结合使用时理解错误【英文标题】:Understanding error when using partial function application or curry in conjunction with overload and std::visit 【发布时间】:2021-09-06 23:08:31 【问题描述】:tl;博士
我想了解下面第一个代码有什么问题,即错误告诉我什么。
MRE
我已经可以shorten the example to the following,这会产生与下面的原始代码相同的错误:
#include <boost/hana/functional/overload.hpp>
auto l1 = [](int); using L1 = decltype(l1);
auto l2 = [](double); using L2 = decltype(l2);
auto l3 = [](float); using L3 = decltype(l3);
using Ovl = boost::hana::overload_t<L1, L2, L3>
//const // uncomment to prevent error
;
Ovl ovll1,l2,l3;
Ovl ovl2ovl; // this triggers the compilation error
原问题
当我尝试将std::visit
部分应用于通过boost::hana::overload
获得的访问者函数时,我无法理解我得到的错误。 (我知道我不能传递模板函数的名称,所以我不能真正部分应用 std::visit
,所以我将它包装在一个名为 visit
的通用 lambda 中;我没有费心去做完美转发,因为我几乎不相信它与问题相关。)
#include <boost/hana/functional/overload.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/functional/curry.hpp>
#include <iomanip>
#include <iostream>
#include <variant>
#include <vector>
using var_t = std::variant<int, long, double, std::string>;
int main()
std::vector<var_t> vec = 10, 15l, 1.5, "hello";
auto visitor = boost::hana::overload([](auto arg) std::cout << arg << ' '; ,
[](double arg) std::cout << std::fixed << arg << ' '; ,
[](const std::string& arg) std::cout << std::quoted(arg) << ' ';
);
auto visit = [](auto const& visitor_, auto const&... visited_)
return std::visit(visitor_, visited_...);
;
for (auto& v: vec)
boost::hana::partial(visit, visitor)(v); // the error is essentially
boost::hana::curry<2>(visit)(visitor)(v); // the same on these 2 lines
std::cout << std::endl;
错误是(godbolt):
In file included from deleteme.cpp:1:
/usr/include/boost/hana/functional/overload.hpp: In instantiation of ‘constexpr boost::hana::overload_t<F, G>::overload_t(F_&&, G_&& ...) [with F_ = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; G_ = ; F = main()::<lambda(auto:22)>; G = main()::<lambda(double)>, main()::<lambda(const string&)>]’:
/usr/include/boost/hana/detail/ebo.hpp:62:36: required from ‘constexpr _hana::ebo<K, V, true>::ebo(T&&) [with T = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; K = boost::hana::detail::bti<1>; V = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >]’
/usr/include/boost/hana/basic_tuple.hpp:70:65: required from ‘constexpr boost::hana::detail::basic_tuple_impl<std::integer_sequence<long unsigned int, _Idx ...>, Xn ...>::basic_tuple_impl(Yn&& ...) [with Yn = main()::<lambda(const auto:25&, const auto:26& ...)>, boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; long unsigned int ...n = 0, 1; Xn = main()::<lambda(const auto:25&, const auto:26& ...)>, boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >]’
/usr/include/boost/hana/basic_tuple.hpp:97:44: required from ‘constexpr boost::hana::basic_tuple<Xs>::basic_tuple(Yn&& ...) [with Yn = main()::<lambda(const auto:25&, const auto:26& ...)>, boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; Xn = main()::<lambda(const auto:25&, const auto:26& ...)>, boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >]’
/usr/include/boost/hana/functional/partial.hpp:71:15: required from ‘constexpr boost::hana::partial_t<std::integer_sequence<long unsigned int, _Idx ...>, F, X ...>::partial_t(boost::hana::make_partial_t::secret, T&& ...) [with T = main()::<lambda(const auto:25&, const auto:26& ...)>, boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; long unsigned int ...n = 0; F = main()::<lambda(const auto:25&, const auto:26& ...)>; X = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >]’
/usr/include/boost/hana/functional/partial.hpp:61:74: required from ‘constexpr boost::hana::partial_t<std::integer_sequence<long unsigned int, __integer_pack()(sizeof ... (X ...))...>, typename boost::hana::detail::decay<Xs>::type, typename boost::hana::detail::decay<X, typename std::remove_reference<X>::type>::type ...> boost::hana::make_partial_t::operator()(F&&, X&& ...) const [with F = main()::<lambda(const auto:25&, const auto:26& ...)>; X = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; typename boost::hana::detail::decay<Xs>::type = main()::<lambda(const auto:25&, const auto:26& ...)>; typename std::remove_reference<_Tp>::type = main()::<lambda(const auto:25&, const auto:26& ...)>]’
/usr/include/boost/hana/functional/curry.hpp:149:24: required from ‘constexpr decltype(auto) boost::hana::curry_t<n, F>::operator()(X&& ...) && [with X = boost::hana::overload_t<main()::<lambda(auto:22)>, main()::<lambda(double)>, main()::<lambda(const string&)> >&; long unsigned int n = 2; F = main()::<lambda(const auto:25&, const auto:26& ...)>]’
deleteme.cpp:31:37: required from here
/usr/include/boost/hana/functional/overload.hpp:53:61: error: no matching function for call to ‘boost::hana::overload_t<main()::<lambda(double)>, main()::<lambda(const string&)> >::overload_t()’
53 | , overload_t<G...>::type(static_cast<G_&&>(g)...)
| ^
/usr/include/boost/hana/functional/overload.hpp:51:28: note: candidate: ‘template<class F_, class ... G_> constexpr boost::hana::overload_t<F, G>::overload_t(F_&&, G_&& ...) [with F_ = F_; G_ = G_ ...; F = main()::<lambda(double)>; G = main()::<lambda(const string&)>]’
51 | constexpr explicit overload_t(F_&& f, G_&& ...g)
| ^~~~~~~~~~
/usr/include/boost/hana/functional/overload.hpp:51:28: note: template argument deduction/substitution failed:
/usr/include/boost/hana/functional/overload.hpp:53:61: note: candidate expects at least 1 argument, 0 provided
53 | , overload_t<G...>::type(static_cast<G_&&>(g)...)
| ^
/usr/include/boost/hana/functional/overload.hpp:42:12: note: candidate: ‘constexpr boost::hana::overload_t<main()::<lambda(double)>, main()::<lambda(const string&)> >::overload_t(const boost::hana::overload_t<main()::<lambda(double)>, main()::<lambda(const string&)> >&)’
42 | struct overload_t
| ^~~~~~~~~~
/usr/include/boost/hana/functional/overload.hpp:42:12: note: candidate expects 1 argument, 0 provided
/usr/include/boost/hana/functional/overload.hpp:42:12: note: candidate: ‘constexpr boost::hana::overload_t<main()::<lambda(double)>, main()::<lambda(const string&)> >::overload_t(boost::hana::overload_t<main()::<lambda(double)>, main()::<lambda(const string&)> >&&)’
/usr/include/boost/hana/functional/overload.hpp:42:12: note: candidate expects 1 argument, 0 provided
但是,如果我将 visit
定义为“手动”咖喱,它的行为与我预期的一样 (godbolt):
#include <boost/hana/functional/overload.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/functional/curry.hpp>
#include <iomanip>
#include <iostream>
#include <variant>
#include <vector>
// the variant to visit
using var_t = std::variant<int, long, double, std::string>;
int main()
std::vector<var_t> vec = 10, 15l, 1.5, "hello";
auto visitor = boost::hana::overload([](auto arg) std::cout << arg << ' '; ,
[](double arg) std::cout << std::fixed << arg << ' '; ,
[](const std::string& arg) std::cout << std::quoted(arg) << ' ';
);
auto visit_c = [](auto const& visitor_)
return [&visitor_](auto const&... visited_)
return std::visit(visitor_, visited_...);
;
;
for (auto& v: vec)
visit_c(visitor)(v);
std::cout << std::endl;
(示例改编自cppreference。)
【问题讨论】:
我不确定,文档对此并不清楚,但我认为问题在于partial
和 curry
不适用于可变参数可调用对象
@Caleth, don't they?
我将boost::hana::overload
换成了一个为每种类型定义了operator()
的结构,它似乎可以编译。不是真正的答案,但有点有趣
似乎递归继承未能检测到基本情况并试图形成overload_t<>
,可能是因为它包含的类型本身就是overload_t
特化。
FWIW,使visitor
const 足以让它编译:godbolt.org/z/Wf4jMq4Me
【参考方案1】:
在上面的 MRE 之上,我已经验证了这些断言都通过了,
static_assert(std::is_base_of_v<L1, Ovl>);
static_assert(std::is_base_of_v<boost::hana::overload_t<L2, L3>::type, Ovl>);
static_assert(std::is_base_of_v<boost::hana::overload_t<L2, L3>, Ovl>);
static_assert(std::is_base_of_v<L2, boost::hana::overload_t<L2, L3>::type>);
static_assert(std::is_base_of_v<L3, boost::hana::overload_t<L2, L3>::type>);
其中,鉴于boost::hana::ovearload_t
的写法,意味着boost::hana::overload_t<L1, L2, L3>
的实例化具有这种形式(不是有效的C++,这里我扮演编译器的角色):
struct overload_t<L1, L2, L3>
: L1
, overload_t<L2, L3>
using type = overload_t;
using L1::operator();
using overload_t<L2, L3>::operator();
template <typename F_, typename ...G_>
constexpr explicit overload_t(F_&& f, G_&& ...g)
: L1(static_cast<F_&&>(f))
, overload_t<L2, L3>(static_cast<G_&&>(g)...) // error points here
;
因此,当编译器看到Ovl ovl2ovl;
时,它必须从ovl
构造ovl2
;要做到这一点,有两种可能:
-
用
F_ = overload_t<L1, L2, L3>&
和一个空包G_...
实例化template <typename F_, typename ...G_> constexpr explicit overload_t<L1, L2, L3>::overload_t(F_&& f, G_&& ...g)
,这会生成一个带有签名overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&)
的构造函数,这对于复制一个非const
很有用左值(如ovl
);
使用复制构造函数,用户定义的模板构造函数不会阻止其隐式定义(参见 Scott Meyers 的 Effective Modern C++,第 115 页,第 17 项)。
然而,隐式定义的复制构造函数有签名overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3> const&)
,当overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&)
被输入非const
左值时,它的匹配度不如overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&)
ovl
,所以编译器将不得不选择上面的第一个替代方案,这反过来会使其尝试构造overload_t<L2, L3>()
,它很好地映射到错误:
error: no matching function for call to
‘boost::hana::overload_t<<lambda(double)>, <lambda(float)> >::overload_t()’
使ovl
const
解决了这个问题,因为它使隐式定义的复制构造函数overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3> const&)
完全匹配;模板构造函数在使用F_ = overload_t<L1, L2, L3> const&
实例化时也是完全匹配的,但前者是首选,因为它没有模板化。
std::move
ing ovl
也解决了这个问题,原因相同:隐式定义的复制构造函数是复制右值的精确匹配,它优于模板构造函数的同样精确匹配。
【讨论】:
以上是关于使用偏函数应用程序或 curry 与重载和 std::visit 结合使用时理解错误的主要内容,如果未能解决你的问题,请参考以下文章
std::to_string - 多个重载函数的实例与参数列表匹配