了解将部分函数应用程序或 curry 与 overload 和 std::visit 结合使用时的错误

Understanding error when using partial function application or curry in conjunction with overload and std::visit

提问人:Enlico 提问时间:6/23/2021 最后编辑:Enlico 更新时间:11/21/2021 访问量:153

问:

TL的;博士

我想了解下面的第一个代码有什么问题,即错误告诉我什么。

MRE系列

我已经能够将示例缩短为以下内容,这会生成与下面的原始代码相同的错误:

#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 ovl{l1,l2,l3};

Ovl ovl2{ovl}; // this triggers the compilation error

原始问题

当我尝试部分应用于通过 .(我知道我不能传递模板函数的名称,所以我不能真正部分应用,所以我把它包装在一个名为 just 的通用 lambda 中;我没有费心去做完美的转发,因为我几乎不相信它与问题有关。std::visitboost::hana::overloadstd::visitvisit

#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

但是,如果我定义为“手动”咖喱,它的行为符合我的预期(godbolt):visit

#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

C++ 函数式编程 重载 复制构造函数 boost-hana

评论

0赞 Caleth 6/23/2021
我不确定,文档对此也不清楚,但我认为问题在于,不适用于可变参数可调用对象partialcurry
0赞 Enlico 6/23/2021
@Caleth,不是吗?
1赞 mattlangford 6/23/2021
我将其切换为为每种类型定义的结构,它似乎可以编译。不是真正的答案,但有点有趣boost::hana::overloadoperator()
1赞 Davis Herring 6/23/2021
似乎递归继承未能检测到基本情况并试图形成,也许是因为它的包含类型本身就是一种特殊化。overload_t<>overload_t
0赞 ildjarn 11/19/2021
FWIW,使 const 足以使其编译:godbolt.org/z/Wf4jMq4Mevisitor

答:

0赞 Enlico 11/20/2021 #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 的编写方式,这意味着 的实例化具有这种形式(C++无效,在这里我扮演编译器的角色):boost::hana::overload_t<L1, L2, L3>

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
    { }
};

因此,当编译器看到 时,它必须构造 from;为此,有两种可能性:Ovl ovl2{ovl};ovl2ovl

  1. 实例化 with 和 一个空包 ,这会产生一个具有签名的构造函数,该构造函数适合复制一个非常(例如template <typename F_, typename ...G_> constexpr explicit overload_t<L1, L2, L3>::overload_t(F_&& f, G_&& ...g)F_ = overload_t<L1, L2, L3>&G_...overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&)ovl);
  2. 使用复制构造函数,其隐式定义不会被用户定义的模板构造函数阻止(参见Scott Meyers的Effective Modern C++,第115页,第17项)。

然而,隐式定义的复制构造函数具有 signature ,这不如当它们被提供非常 lvalue 时那么好匹配,因此编译器将不得不选择上面的第一个替代方案,这反过来又会让它尝试构造,它很好地映射到错误: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>&)ovloverload_t<L2, L3>()

error: no matching function for call to
‘boost::hana::overload_t<<lambda(double)>, <lambda(float)> >::overload_t()’

Making 解决了这个问题,因为它使隐式定义的复制构造函数完全匹配;模板构造函数在实例化时也是完全匹配的,但前者是首选,因为它没有模板化。ovlconstoverload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3> const&)F_ = overload_t<L1, L2, L3> const&

std::move出于同样的原因,ing 也解决了这个问题:隐式定义的复制构造函数是复制右值的精确匹配,并且它比模板化构造函数的同样精确匹配更可取。ovl