4

tl;dr

I'd like to understand what's wrong with the first code below, i.e. what the error is telling me.

MRE

I've been able to shorten the example to the following, which generates the same error as the original code below:

#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

Original question

I'm having trouble understanding the errors I get when I try partially applying std::visit to a visitor function obtained via boost::hana::overload. (I know that I can't pass around the name of a template function, so I can't really partially apply std::visit, so I wrapped it in a generic lambda named just visit; I've not bothered doing perfect forwarding because I hardly believe it's relevant to the question.)

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

The error is (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

However, if I define visit to be "manually" curried, it behaves as I expect (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;
}

(Example adapted from cppreference.)

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • I'm not sure, and the documentation isn't clear on this, but I think the problem is that `partial` and `curry` don't work for variadic callables – Caleth Jun 23 '21 at 12:08
  • @Caleth, [don't they?](https://godbolt.org/z/M3h6KE9bj) – Enlico Jun 23 '21 at 13:04
  • 1
    I switched the `boost::hana::overload` out for a struct with `operator()` defined for each of the types and it seemed to compile. Not really an answer, but kind of interesting – mattlangford Jun 23 '21 at 14:20
  • 1
    It seems that the recursive inheritance is failing to detect the base case and trying to form `overload_t<>`, perhaps because its contained type is itself an `overload_t` specialization. – Davis Herring Jun 23 '21 at 15:46
  • FWIW, making `visitor` const is sufficient to get it to compile: https://godbolt.org/z/Wf4jMq4Me – ildjarn Nov 19 '21 at 10:10
  • @DavisHerring, I have shortened the reproduction example. I think your comment is close to an answer... – Enlico Nov 20 '21 at 10:48

1 Answers1

0

On top of the above MRE, I've verified that these assertions all pass,

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>);

which, given how boost::hana::ovearload_t is written, means that the instantiation of boost::hana::overload_t<L1, L2, L3> has this form (not valid C++, here I play the role of the compiler):

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

Therefore, when the compiler sees Ovl ovl2{ovl};, it has to construct ovl2 from ovl; to do so, there are two possibilities:

  1. instantiating template <typename F_, typename ...G_> constexpr explicit overload_t<L1, L2, L3>::overload_t(F_&& f, G_&& ...g) with F_ = overload_t<L1, L2, L3>& and an empty pack G_..., which results in a constructor with signature overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&) which is good for copy a non-const lvalue (such as ovl);
  2. using the copy constructor, the implicit definition of which is not prevented by the user-defined template constructor (see Effective Modern C++ by Scott Meyers, page 115, Item 17).

The implicitly defined copy constructor, however, has signature overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3> const&), which is not as good a match as overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3>&) when they are fed with the non-const lvalue ovl, so the compliler will have to choose the first alternative above, which will in turn make it try the construction overload_t<L2, L3>(), which maps nicely to the error:

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

Making ovl const solves the problem because it makes the implicitly defined copy constructor, overload_t<L1, L2, L3>::overload_t(overload_t<L1, L2, L3> const&) an exact match; the template constructor too is an exact match when instantiated with F_ = overload_t<L1, L2, L3> const&, but the former is preferred because it is not templated.

std::moveing ovl too solves the problem, for the same reason: the implicitly defined copy constructor is an exact match for copying an rvalue, and it is preferred to the likewise exact match of the templated constructor.

Enlico
  • 23,259
  • 6
  • 48
  • 102