Search code examples
c++booststructc++14boost-hana

Can I use `hana::keys` without an instance of the structure?


The documentation for hana::keys says that I can use it in function call syntax e.g. hana::keys(s) where s is an instance of a class meeting the concept hana::Struct, and it returns a sequence of key objects.

A related function, hana::accessors, returns a sequence of accessor functions which can be used to grab the corresponding members from an instance of the struct.

hana::accessors can be used in two ways:

hana::accessors(s)
hana::accessors<S>()

are both legal, constexpr functions returning the same thing when S = decltype(s) -- the sequence corresponding to structure S.

When I try this syntax with hana::keys, I get an error. Here's an MCVE, adapted from an example in hana documentation:


#include <boost/hana.hpp>
#include <boost/hana/define_struct.hpp>
#include <boost/hana/keys.hpp>
#include <iostream>
#include <string>

namespace hana = boost::hana;

struct Person {
    BOOST_HANA_DEFINE_STRUCT(Person,
        (std::string, name),
        (unsigned short, age)
    );
};


// Debug print a single structure

template <typename T>
void debug_print_field(const char * name, const T & value) {
  std::cout << "\t" << name << ": " << value << std::endl;
}


template <typename S>
void debug_print(const S & s) {
  std::cout << "{\n";

  hana::for_each(hana::keys<S>(), [&s] (auto key) {
    debug_print_field(hana::to<char const *>(key), hana::at_key(s, key));
  });

  std::cout << "}" << std::endl;
}

// Debug print compare two structures

int main() {
    Person john{"John", 30}, kevin{"Kevin", 20};

    debug_print(john);
    std::cout << std::endl;
    debug_print(kevin);
    std::cout << std::endl;
}

$ g++-6 -std=c++14 -I/home/chris/boost/boost_1_61_0/ main.cpp
main.cpp: In function ‘void debug_print(const S&)’:
main.cpp:28:30: error: expected primary-expression before ‘>’ token
   hana::for_each(hana::keys<S>(), [&s] (auto key) {
                              ^
main.cpp:28:32: error: expected primary-expression before ‘)’ token
   hana::for_each(hana::keys<S>(), [&s] (auto key) {
                                ^

It works fine when I use hana::keys(s).

But in my actual application, I don't have an instance of the structure, it's only a template parameter.

As a hack, I made this:

// Work around for `hana::keys`

template <typename S>
constexpr decltype(auto) get_hana_keys() {
  return decltype(hana::keys(std::declval<S>())){};
}

I believe that this works based on my limited understanding of implementation details of hana described in the docu. -- hana::keys is supposed to return a sequence of compile-time strings, and all of the information is contained in the type, so just getting the type and default constructing it should be equivalent.

When I use get_hana_keys<S>() in my MCVE then it compiles and runs fine.

However I don't know if it's really correct, or if the assumptions I'm making go beyond what the docs allow me to assume.

I am using boost version 1.61 and gcc 6.2.0.

What I would like to know is,

  • Is there a good reason that hana::keys<S>() doesn't work or is this simply an oversight? hana seems to have been very meticulously designed so I'm inclined to second-guess myself here.

  • Is there anything wrong with the hack I created or a way to improve it?


Solution

  • Good question!

    Is there a good reason that hana::keys<S>() doesn't work or is this simply an oversight?

    The reason why hana::keys<S>() does not work is that there's no way to implement it in the general case. Indeed, keys was initially designed for hana::map, where the keys could be stateful and so you really need an object to return something meaningful. The fact that an object is not necessary to retrieve the keys of a hana::Struct is just a coincidence.

    Is there anything wrong with the hack I created or a way to improve it?

    Technically, hana::string is not documented as being default-constructible, so default-constructing a hana::tuple of those is not guaranteed to work. However, this is an oversight which I've fixed in 1eebdb, so you're good.

    That being said, a perhaps more idiomatic solution would be the following:

    template <typename S>
    constexpr auto get_hana_keys() {
      return hana::transform(hana::accessors<S>(), hana::first);
    }
    

    In fact, this is how we define hana::keys for hana::Structs.

    Finally, note that everything related to hana::Struct will be much better served by language-level reflection, so please excuse the quirky support for reflection that Hana provides. It's really hard to do anything good in that area without language support.