c++c++17template-meta-programmingsfinaeboost-hana

Difficulty in verifying valide calls with boost::hana::is_valid


I have a class Foo that can be constructed from C-style strings, string views and non-temporary strings (in reality it contains other members and methods, and it is templated on the character to pass to basic_string*s templates):

struct Foo {
    explicit constexpr Foo()
        : text{}
    {}
    explicit constexpr Foo(std::string_view text)
        : text{std::move(text)}
    {}
    explicit constexpr Foo(char const* text)
        : Foo{std::string_view{text}}
    {}
    explicit constexpr Foo(char* text)
        : Foo{std::string_view{text}}
    {}
    explicit constexpr Foo(std::string&&) = delete;

    std::string_view text;
};

With the help of Boost.Hana, I can assert what Foo can be constructed from and what not, for documentation purposes, in a test:

for_each(make_basic_tuple(
            type_c<std::string>,
            // clearly I'm not also listing type_c<int> and all the countless imaginable types that wouldn't work
            type_c<std::string&&>
            ),
        [](auto t){
            static_assert(!std::is_constructible_v<Foo, typename decltype(t)::type>);
        });
                                                                                      
for_each(make_basic_tuple(
            type_c<char*>,
            type_c<char const*>,
            // ...
            type_c<std::string_view>,
            type_c<std::string const&>
            ),
        [](auto t){
            static_assert(std::is_constructible_v<Foo, typename decltype(t)::type>);
        });

But via Boost.Hana, a make_line helper function is defined too:

namespace boost::hana {
template <>
struct make_impl<Foo> {
    static constexpr Foo apply(const char* text) {
        return Foo{text};
    }
    static constexpr Foo apply(std::string const& text) {
        return Foo{text};
    }
    static constexpr Foo apply(std::string_view text) {
        return Foo{std::move(text)};
    }
    static constexpr Foo apply(std::string&&) = delete;
};
}

inline constexpr auto make_foo = boost::hana::make<Foo>;

and I can easily verify that it works only with the intendend category values of the arguments:

make_foo("");
make_foo(""sv);
make_foo(sv);
make_foo(s);
//make_foo(std::move(s)); // correctly doesn't compile
//make_foo(""s);          // correctly doesn't compile

However, I'm not able to write this in a test via hana::is_valid. Here's my failed attampt:

std::string s{};
std::string_view sv{};
                                                                                        
constexpr auto can_make_foo_from =
    is_valid([](auto&& obj) -> decltype(make_foo(std::forward<decltype(obj)>(obj))){});
                                                                                        
static_assert( decltype(can_make_foo_from(""))::value);
static_assert( decltype(can_make_foo_from(""sv))::value);
static_assert( decltype(can_make_foo_from(sv))::value);
static_assert( decltype(can_make_foo_from(s))::value);
//static_assert(!decltype(can_make_foo_from(std::move(s)))::value);
//static_assert(!decltype(can_make_foo_from(""s))::value);

where in my intentions the last 2 lines should compile, but they don't.

Here's the full example on Compiler Explorer.


Solution

  • From boost/hana/fwd/core/make.hpp we can see:

    template <typename Tag>
    struct make_t {
        template <typename ...X>
        constexpr decltype(auto) operator()(X&& ...x) const {
            return make_impl<Tag>::apply(static_cast<X&&>(x)...);
        }
    };
    
    template <typename Tag>
    BOOST_HANA_INLINE_VARIABLE constexpr make_t<Tag> make{};
    

    decltype(auto) doesn't trigger SFINAE.

    So any make<MyType>(Ts...) is valid, but can generate hard errors.

    Fix would be:

    template <typename Tag>
    struct make_t {
        template <typename ...X>
        constexpr auto operator()(X&& ...x) const
        -> decltype(make_impl<Tag>::apply(static_cast<X&&>(x)...))
        {
            return make_impl<Tag>::apply(static_cast<X&&>(x)...);
        }
    };
    

    Demo