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