使用偏函数应用程序或 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。)

【问题讨论】:

我不确定,文档对此并不清楚,但我认为问题在于 partialcurry 不适用于可变参数可调用对象 @Caleth, don't they? 我将boost::hana::overload 换成了一个为每种类型定义了operator() 的结构,它似乎可以编译。不是真正的答案,但有点有趣 似乎递归继承未能检测到基本情况并试图形成overload_t&lt;&gt;,可能是因为它包含的类型本身就是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&lt;L1, L2, L3&gt;的实例化具有这种形式(不是有效的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&lt;L1, L2, L3&gt;&amp; 和一个空包G_... 实例化template &lt;typename F_, typename ...G_&gt; constexpr explicit overload_t&lt;L1, L2, L3&gt;::overload_t(F_&amp;&amp; f, G_&amp;&amp; ...g),这会生成一个带有签名overload_t&lt;L1, L2, L3&gt;::overload_t(overload_t&lt;L1, L2, L3&gt;&amp;) 的构造函数,这对于复制一个const 很有用左值(如ovl); 使用复制构造函数,用户定义的模板构造函数不会阻止其隐式定义(参见 Scott Meyers 的 Effective Modern C++,第 115 页,第 17 项)。

然而,隐式定义的复制构造函数有签名overload_t&lt;L1, L2, L3&gt;::overload_t(overload_t&lt;L1, L2, L3&gt; const&amp;),当overload_t&lt;L1, L2, L3&gt;::overload_t(overload_t&lt;L1, L2, L3&gt;&amp;)被输入const左值时,它的匹配度不如overload_t&lt;L1, L2, L3&gt;::overload_t(overload_t&lt;L1, L2, L3&gt;&amp;) ovl,所以编译器将不得不选择上面的第一个替代方案,这反过来会使其尝试构造overload_t&lt;L2, L3&gt;(),它很好地映射到错误:

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

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

std::moveing ovl 也解决了这个问题,原因相同:隐式定义的复制构造函数是复制右值的精确匹配,它优于模板构造函数的同样精确匹配。

【讨论】:

以上是关于使用偏函数应用程序或 curry 与重载和 std::visit 结合使用时理解错误的主要内容,如果未能解决你的问题,请参考以下文章

std::to_string - 多个重载函数的实例与参数列表匹配

基于值与常量引用的函数重载

JavaScript ES6函数式编程:柯里化偏应用组合管道

函数重载(续)==》函数重载和函数指针在一起

Scala笔记--函数式编程

为啥 C++ 中的 main() 没有重载以使用 std::string?