I have a template function that has no definition by default but it specialised by some types:
template <typename T>
auto foo(bar &, const T &) -> void;
template <>
auto foo<std::string>(bar &, const std::string &) -> void {}
How do I write a constexpr function that tells me if type T has a specialisation for the above function?
My best effort:
namespace detail {
auto has_foo(hana::is_valid([](auto &b, const auto &t) -> decltype(foo(b, t)) {}));
} // namespace detail
template <typename T>
constexpr auto has_foo() -> bool
{
using hana::type_c;
return detail::has_foo(type_c<bar>, type_c<T>);
}
static_assert(has_foo<std::string>());
This static assert fires, however, which I would expect it not to if I had got this right.
The problem here is that you're passing hana::type
s to a function that expects actual objects. When you write detail::has_foo(type_c<bar>, type_c<T>)
, Hana passes the hana::type_c
s as-is to detail::has_foo
. But since foo
can't be called with hana::type
s, it fails. Instead, you have two options. The first option is to keep on passing hana::type
s to detail::has_foo
, but to use declval
inside has_foo
(note that I have added the proper ref-qualifiers to bar
and T
):
#include <boost/hana.hpp>
#include <string>
namespace hana = boost::hana;
struct bar { };
template <typename T>
auto foo(bar&, T const&) -> void;
template <>
auto foo<std::string>(bar&, std::string const&) -> void { }
namespace detail {
auto has_foo = hana::is_valid([](auto b, auto t) -> decltype(
foo(hana::traits::declval(b), hana::traits::declval(t))
) { });
}
template <typename T>
constexpr auto has_foo() -> bool {
return detail::has_foo(hana::type_c<bar&>, hana::type_c<T const&>);
}
static_assert(has_foo<std::string>(), "");
The other option is to drop the usage of hana::type
altogether and to pass actual objects to detail::has_foo
:
namespace detail {
auto has_foo = hana::is_valid([](auto& b, auto const& t) -> decltype(foo(b, t)) { });
}
template <typename T>
constexpr auto has_foo() -> decltype(
detail::has_foo(std::declval<bar&>(), std::declval<T const&>())
) { return {}; }
Here, I'm using std::declval
to do as-if I had objects of the proper type, and then I call detail::has_foo
with those "objects". Which one you pick is mainly a matter of preference. Also, depending on your use case, perhaps the actual objects are available when you call has_foo
. If this is the case, you could refactor to
namespace detail {
auto has_foo = hana::is_valid([](auto& b, auto const& t) -> decltype(foo(b, t)) { });
}
template <typename T>
constexpr auto has_foo(bar& b, T const& t) -> decltype(detail::has_foo(b, t)) { return {}; }
C++17 will make our life much easier by removing the ban on lambdas in constant expressions, which will allow you to write
constexpr auto has_foo = hana::is_valid([](bar& b, auto const& t) -> decltype(foo(b, t)) { });
thus removing the need for an external helper.
Also note that you're not specifically testing whether foo
has a specialization for T
, but really whether the foo(...)
expression is well-formed. This can be subtly different in the presence of overloads or ADL, but it should be sufficient for most use cases. I'm not sure if it's possible to precisely check whether a function is specialized for some type.
Hope this helps!