Search code examples
c++c++17template-meta-programmingsfinaeboost-hana

What is the advantage of Hana's type_c-and-declval dance when querying whether a SFINAE-friendly expression is valid?


On the one hand, the function boost::hana::is_valid is presented as follows

Checks whether a SFINAE-friendly expression is valid.

Given a SFINAE-friendly function, is_valid returns whether the function call is valid with the given arguments. Specifically, given a function f and arguments args...,

is_valid(f, args...) == whether f(args...) is valid

The result is returned as a compile-time Logical.

and an example of the usage accompanies it (from the same linked page):

    struct Person { std::string name; };
    auto has_name = hana::is_valid([](auto&& p) -> decltype((void)p.name) { });
 
    Person joe{"Joe"};
    static_assert(has_name(joe), "");
    static_assert(!has_name(1), "");

where we see that the lambda fed to is_valid is in turn fed with the actual object that we feed to has_name.

On the other hand, the book C++ Templates - The Complete Guide presents a very similar solution (and indeed the authors cite Boost.Hana and Loius Dionne), which I omit for now the details of. This solution, however, is used in a slightly different way:

constexpr auto hasFirst = isValid([](auto x) -> decltype((void)valueT(x).first) {});
static_assert(!hasFirst(type<int>));
struct S { int first; };
static_assert(hasFirst(type<S>));

The above assumes the existence of valueT and type defined/declared below

template<typename T>
struct TypeT {
  using Type = T;
};

template<typename T>
constexpr auto type = TypeT<T>{};

template<typename T>
T valueT(TypeT<T>);

Now, if I understand correctly, valueT and type correspond roughly to boost::hana::traits::declval and boost::hana::type_c, so the example from the book should map to the following

constexpr auto hasFirstH = is_valid([](auto x) -> decltype((void)traits::declval(x).first) {});
static_assert(!hasFirst(hana::type_c<int>));
struct S { int first; };
static_assert(hasFirst(hana::type_c<S>));

But what is the advantage of this?

In this answer from Louis Dionne I initially understood that it's a matter of taste, but then I thought that might be the case for that specific scenario and not in general.


Solution

  • While writing the question, I've searched more an more (to put relevant links in it, mainly), and I eventually did find the answer in the documentation of Boost.Hana at Boost.Hana > User Manual > Introspection > Checking expression validity > Non-static members: the use of hana::type_c to wrap a type T in an object (not of type T, but of type hana::type<T>!) and hana::declval to unwrap it is useful to write those type traits when there's no object around.