c++c++14overload-resolutionboost-hanareferential-transparency

What does boost::hana::always do more than just "always return its first argument"?


At the doc page of boost::hana::always I read that

always(x) is a function such that

always(x)(y...) == x

for any y....

This makes me think that it shouldn't behave any differently than this lambda: [](auto const&...){ return false; }.

However it does. For instance, the following code prints 11, but if I change the third lambda to hana::always(false), then it prints 00, revealing that always is swallowing any argument.

#include <boost/hana/functional/always.hpp>
#include <boost/hana/functional/overload.hpp>
#include <iostream>

auto fun = boost::hana::overload(
        [](int){ return true; },
        [](double){ return true; },
        [](auto const&...){ return false; }
        );

int main() {
    std::cout << fun(1) << fun(1.0) << std::endl;
}

By the way, I've just discovered boost::hana::overload_linearly, which is not an alternative to boost::hana::overload in this case (because as much as always would not get all the calls, it would be the [](int){ return true; } to be greedy), but it's good to know of it.


By the way, the amount of scenarii where this quirk can bite you is astonishing! Here's another very simplified case: the code below fails to compile for the reason perfectly explained in the accepted answer, and turning either 1 to 0 make it compile.

#include <concepts>
#include <boost/hana/functional/always.hpp>

template <typename P>
struct Foo {

    template<typename E>
    requires requires (P p, E e) {
#if 1
        { p(e, e) } -> std::same_as<bool>;
#else
        { p(e, e) } -> std::convertible_to<bool>;
#endif
    }
    auto operator()(E&&) const {
    }

    P p;
};
#if 1
auto const foo = Foo{boost::hana::always(true)};
#else
auto const foo = Foo{[](auto const& ...){ return true; }};
#endif

int main() {
    int bar;
    foo(bar);
}

I was so puzzled by the fact that I could require that a thing I know to be a predicate would return something std::convertible_to<bool>, but not something that is std::same_as<bool>!


Solution

  • In fact, always has different overloads (as it handles reference as return value).

    so, with a simplified version:

    template <typename T>
    struct my_always
    {
         T res;
    
         template <typename ...&& Ts>
         T& operator ()(Ts&&...) /* mutable */ { return res; } // #1
    
         template <typename ...&& Ts>
         const T& operator ()(Ts&&...) const { return res; } // #2
    };
    

    then

    auto fun = boost::hana::overload(
            my_always<bool>{ false }, // #1 & #2
            [](int){ return true; }   // #3
            );
    
    std::cout << fun(1);
    

    Possible overloads are:

    All are viable here, but #1 is the best match (as fun is not const (and int is not better than int&&)).

    With const fun, #3 would be the best match (#1 not viable, tie-breaker between #2 and #3 is template status).