Boost logo

Boost :

Subject: Re: [boost] [Hana] Migrating sqlpp11 to Hana (hypothetically)
From: Louis Dionne (ldionne.2_at_[hidden])
Date: 2015-06-15 15:08:29


Roland Bock <rbock <at> eudoxos.de> writes:

>
> Hi Louis,
>
> First of all: Hats off to you, your library and the documentation!

Thank you.

> [...]
>
> _Compile__time strings:_
> This one seems easy. There probably won't be a C++14 version of sqlpp11,
> but there will be a C++17 version, hopefully allowing me to use the
> string literal without relying on a non-standard compiler extension.
>
> This is what I currently have [2]:
>
> struct _alias_t
> {
> static constexpr const char _literal[] = "delta";
> using _name_t = sqlpp::make_char_sequence<sizeof(_literal),
> _literal>;
> };
>
> This is what I would like to do:
>
> struct _alias_t
> {
> using _name_t = decltype("delta"_s);
> };
>
> But the whole thing is in a header.
>
> * I do not want to "using namespace ::boost::hana::literals" at
> top-level or in one of the namespaces in the header.

Is it acceptable to only import the `_s` string literal from Hana?

    using boost::hana::literals::operator""_s;

> * Neither one of the following lines is valid:
> o using _name_t = decltype(::boost::hana::operator"_s"("delta"));

To be valid, that would have to be:

    boost::hana::literals::operator""_s<char, 'd', 'e', 'l', 't', 'a'>()

But that's obviously not a good solution.

> o using _name_t =
> decltype("delta"::boost::hana::literals::operator"_s");
> o using _name_t =
> decltype("delta"::boost::hana::literals::operator""_s);
>
> Is there a valid version? Obviously, I would also be happy with
> something like
> using _name_t = decltype(ct_string("delta"));
> * I also cannot use the BOOST_HANA_STRING macro, btw, because that
> would be a lambda in a decltype...

Yeah, the decltype + lambda restriction is annoying. Frankly, I don't know of
any way to use UDLs from another namespace without importing them, and I'm
not sure it's feasible.

> I want to gradually migrate to Hana, so I'd really like to use a type
> here, not a variable. But I could not even use a variable, because the
> following does not compile (clang-3.5.1)
>
> struct A
> {
> static constexpr auto hana_name = BOOST_HANA_STRING("cheesecake");
> };
>
> Any hints?

You can't use constexpr because of the lambda used by the macro. However,
you can do the following:

    struct A {
        static constexpr auto hana_name = "cheesecake"_s;
    };

    struct B {
        using hana_name = decltype("cheesecake"_s);
    };

Though that requires using the non-standard literal extension.
I could add a function to create Hana Strings from a size and a
`constexpr char const*`, but you'd be back to your current method.

> _type sets:_
> Currently I store a bunch of types in type sets to do all kinds of tests
> at compile time. My type set code will need some cleanup or replacement
> one fine day. Hana to the rescue...
>
> Er, wait a second. There is no type set, or is there? There is a set for
> objects (which might represent types). Replacing all those types with
> objects would take some time, but I could live with that, I guess. But
> then I would have to replace all my type_sets (which are types
> themselves) with static constexpr objects, which use tons of constexpr
> template variables. And there are other constructs which use the types I
> now have replaced with objects. So I would have to adjust them, too,
> probably turning even more types into objects.

In theory, that would be the "right" way to do it. What Hana is proposing
is a completely new metaprogramming paradigm. However, in practice, it's
usually possible to isolate the part of the system using Hana from the rest
of the system, or to have Hana interoperate with the rest of the system in
some way. I would say this is something that must be handled on a
case-per-case basis.

For an example of a code base that was partially adapted to Hana with
success, see this fork [1] of the Units-BLAS library by Zach Laine.

> Can I be sure (given today's compilers) that all these objects stay
> away from the generated binary?

I guess you can't be __sure__ of it. My view on this is that as long as
the objects are very small (they're basically empty) and constexpr, the
compiler should be able to optimize it away. If that's not the case, and
if this paradigm turns out to be as great as I advertise, perhaps compiler
writers will have more incentive to apply such optimizations. Also, it is
possible that the language will evolve in the direction of making these
things easier.

However, in practice, you can usually ensure that no objects are generated
at all by doing your computations in a function with value-level syntax, and
then using `decltype` on the result of that function. For example, here's
something taken from the Units-BLAS code I linked above. Here's the original
code (edited for simplicity):

------------------------------------------------------------------------------
namespace detail {
    template <typename HeadRow, typename ...TailRows>
    constexpr auto tuples_same_size(type_sequence<HeadRow, TailRows...>) {
        // some recursive computation returning whether all the Rows have
        // the same std::tuple_size
    }

    template <typename HeadRow, typename ...TailRows>
    struct matrix_type {
        static_assert(
            tuples_same_size(type_sequence<HeadRow, TailRows...>{}),
            "matrix_type<> requires tuples of uniform length"
        );

        using tuple = decltype(std::tuple_cat(
            std::declval<HeadRow>(),
            std::declval<TailRows>()...
        ));
        static const std::size_t rows = sizeof...(TailRows) + 1;
        static const std::size_t columns = std::tuple_size<HeadRow>::value;
        using type = matrix_t<tuple, rows, columns>;
    };
}

template <typename ...Rows>
using matrix = typename detail::matrix_type<Rows...>::type;
------------------------------------------------------------------------------

It basically computes the type of a heterogeneous matrix along with some
sanity checks. It is implemented as a classic metafunction. Now, here's the
code using Hana:

------------------------------------------------------------------------------
namespace detail {
    auto make_matrix_type = [](auto rows) {
        auto nrows = hana::size(rows);
        static_assert(nrows >= 1u,
        "matrix_t<> requires at least one row");

        auto ncolumns = hana::size(rows[hana::size_t<0>]);
        auto uniform = hana::all_of(rows, [=](auto row) {
            return hana::size(row) == ncolumns;
        });
        static_assert(uniform,
        "matrix_t<> requires tuples of uniform length");

        using tuple_type = decltype(hana::flatten(rows));

        return hana::type<matrix_t<tuple_type, nrows, ncolumns>>;
    };
}

template <typename ...Rows>
using matrix = typename decltype(
    detail::make_matrix_type(hana::make_tuple(std::declval<Rows>()...))
)::type;
------------------------------------------------------------------------------

As you can see, the computation that was initially performed inside a struct
was shifted into a lambda (metafunctions become lambdas with Hana). The whole
computation is done using Hana's paradigm. Then, I simply use decltype(...)
on that lambda to get the result of the metafunction. Will code be emitted
because of that lambda, which is a global object? I don't know, but I would
probably file a bug against any compiler who bloated the executable for that.

The trick is to enclose your value-level computations inside functions, and
then use decltype on your functions to ensure they are never actually called.
In practice, doing this in a non-messy way must be studied in a case-per-case
basis, or at least I don't have clear guidelines for now.

> Also, I did not find some of the methods I would expect for sets:
>
> * Create a joined set
> * Create an intersect set
> * Create a difference set
> * Test if two sets are disjunct
> * Test if A is a subset of B, no, strike that, just found it in Searchable
>
> I probably could create the methods above with just a few lines of code
> using the available functions. But it would be convenient to have them
> available, of course (or did I just miss them?).

This is a simple oversight. Like I said elsewhere, the associative sequences
(Set and Map) are still very naive and their interface is incomplete. So far,
I have focused on the simplest and most frequently used structure, Tuple.
I added this issue [2] to remind me of these missing functions.

> By the way, I guess it would be easy for you to compile a list of
> available methods for each type, e.g. Set. Such a mini-index would be
> quite helpful IMO.

Good idea. It would be a pain with my current Doxygen setup, but I think
I'm about to break up with Doxygen anyway. I noted your idea in this
issue [3].

> _any/all/none:
> _Quite a bit of code is tuned to stuff like std::is_same: a type that
> contains a value (true or false). My current versions of any/all/none
> work like that, too.
>
> If I wanted to get rid of my own versions, could I use a drop-in from
> hana? Or would I have to write something like
>
> template<bool... B>
> struct my_all
> {
> static constexpr bool value =
> ::boost::hana::all(::boost::hana::make_tuple(B...));
> };

The best way would be this:

    hana::all(hana::tuple_c<bool, b...>)

This would return a boolean IntegralConstant (which is convertible to bool).
This could also be made just as efficient as your clever implementation using
std::is_same (that you posted on the list a while ago), because we have the
knowledge that the content of the tuple are compile-time bools. However,
using `hana::all(hana::make_tuple(b...))` will be comparatively __very__
inefficient, because we can't know what's in the tuple and we must guarantee
proper short-circuiting.

> _Intermediate summary:_
>
> * I am not sure if I could replace my current compile-time string yet

It depends on which compromises you are willing to do. As far as I know,
you can't do it properly without using the GNU string-literal extension.
Oh wait, I could also write a macro similar to BOOST_MPLLIBS_STRING from
Metaparse, but that wouldn't be very C++14ish.

> * I would like to have a few more convenience methods for Set.

You will get that and much more by the end of the summer, or I won't
get my GSoC T-shirt. :-)

> * It seems to me like if I wanted to use Hana, I should switch to
> Hana-style completely. Otherwise it will just be a weird mixture.

Mixing Hana and classic metaprogramming is indeed very difficult for things
that have to travel a lot in your interface (e.g. compile-time strings that
are exposed to the user). This is simply because Hana is such a different
paradigm that if you want your interface to be Hana-like, then you must
do it all in this way. However, you can still use Hana under the hood to
simplify some metaprogramming locally while keeping your interface as-is.
This is essentially what was done in the Units-BLAS project. But of course,
the benefit of using Hana will then be limited to those local simplifications.

I would be very curious to try and port sqlpp11 to Hana. If you ever want
to give it a shot, please let me know. However, that will have to wait for
a while because I'm neck deep right now.

Regards,
Louis

[1]: https://github.com/tzlaine/Units-BLAS/pull/1
[2]: https://github.com/ldionne/hana/issues/119
[3]: https://github.com/ldionne/hana/issues/120


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk