Implementing static version of std::all_of using template metaprogramming?
Asked Answered
V

2

6

Preface. I'm trying to get somewhat deeper understanding of C++ template metaprogramming and it seems, that I'm stuck... I'm writing a library, which we will use for binary data [de]serialization. The expected structure of data being unpacked is known to a certain extent and it seems reasonable for me to use this knowledge to (1) validate data (2) skip irrelevant parts and (3) unpack the data directly into structs known at compile-time - both for avoiding unnecessary copying and making the client code look cleaner.

So, for example, I want to implement a function which will unpack an array (arrays can contain heterogeneous data, like in JSON). For simplicity, let's say that the array has fixed size, and has no nesting.


The actual problem I want to write a function which will take an input buffer containing serialized data (or a stream - it doesn't matter in our context) and an std::tuple, containing lvalues for output (a parameter pack is a worse alternative, because I'll have to deal with nesting eventually). So, I first need to check, whether all types in a tuple are suitable for the unpacker and to give a relevant error message, if they are not.

So the code is something like:

template<typename T>
struct is_integral_lvalue : std::integral_constant<bool,
                            std::is_lvalue_reference<T>::value &&
                            std::is_integral<T>::value &&
                            (sizeof(T) == 4 || sizeof(T) == 8)>
{
};

/* ... */
template<typename TInputBuffer, typename... TDest>
static TRet unpack_int_tuple(TInputBuffer src_buf, std::tuple<TDest...> &&dest) noexcept(is_noexcept)
{
    static_assert(typelist::all_are<is_integral_lvalue, TDest...>::value,
                  "All types in a tuple must be integral lvalue-references");
    /* do unpacking */
}

The condition is_integral_constant can be somewhat arbitrary. That's why it's desirable that all_are template could use any unary predicate. The question is: what should I write in typelist::all_are (and maybe, what should I fix in the above code to make it possible to write such all_are)?

A working example would be ideal of course, but I'll appreciate general ideas/advices, if they will be helpful.

Limitations My goal is not just to implement this function, but to understand how it works (a solution like "just use boost::mpl" or "boost::hana" is not appropriate). The less unrelated stuff we use, the better. Preferably the code should be in C++11 (we are not ready to use C++1y/GCC 4.9 on production yet). I also hope, that it's possible to avoid using preprocessor macros.

Some stuff, that I googled. Boost.MPL, of course could be used, but it's big, it uses slow recursive templates (instead of variadics) and it's hard to understand what's "under the hood". Boost::hana is, unfortunately, based on polymorphic lambdas, which didn't get into C++11. I have seen this https://github.com/Manu343726/Turbo library, but it seems that it requires too many changes in code to use it (to wrap almost every type in it's adapters). It also uses stuff like lazy evaluation (when expanding templates) - it's not needed here, and will make the code much harder to read.

This library https://github.com/ldionne/mpl11 is almost what I need. The problem is, again, with wrappers: and_ is implemented as a special case of foldr metafunction (which is unrolled for better compile-time performance). And they all use use metafunction lifting, lazyness and so on, making it really hard to understand (except, maybe, for experienced functional language programmers). So what would be basically enough for me is explaining, how to skip all those very generalized and sophisticated techniques and write the same and_ template, but in a simpler way (for a more specific use).

Vicar answered 1/12, 2014 at 3:51 Comment(1)
Glad to see someone thinking on using my library, even if its finally rejected. Many thanks :)Dillman
S
7

Until C++17 and fold expressions come along, a straightforward implementation of all_of is:

// base case; actually only used for empty pack
template<bool... values>
struct all_of : std::true_type {};

// if first is true, check the rest
template<bool... values>
struct all_of<true, values...> : all_of<values...> {};

// if first is false, the whole thing is false
template<bool... values>
struct all_of<false, values...> : std::false_type {}; 

In which case the usage becomes

static_assert(all_of<is_integral_lvalue<TDest>::value...>::value,
              "All types in a tuple must be integral lvalue-references");

If you want to keep your original syntax, it's easy with an alias:

template<template <class> class T, class... U>
using all_are = all_of<T<U>::value...>;

Also, there's an error in your is_integral_lvalue - a reference type is not an integral type. Your is_integral check needs to be done on typename remove_reference<T>::type rather than just T.


Edit: here's a simpler implementation of all_of courtesy of @Columbo:

template<bool...> struct bool_pack;

template<bool...values> struct all_of 
    : std::is_same<bool_pack<values..., true>, bool_pack<true, values...>>{};
Serif answered 1/12, 2014 at 4:2 Comment(12)
I wouldn't call that a reference type, given a type T, if you use T&& in a template, you are basically allowing anything to come in, that's what Scott Meyers calls an universal reference .Corinnecorinth
@Corinnecorinth inb4 the terminology is deprecated. What you're looking for now is a "forwarding reference".Alfilaria
@Corinnecorinth The error is in checking std::is_lvalue_reference<T>::value andstd::is_integral<T>::value simultaneously. They can never be both true.Serif
@remyabel OP didn't specified the version of C++ that he wants to use, he gave a preference to C++11, we are already talking about C++17 ?Corinnecorinth
@Corinnecorinth That's sorta contradictory. There isn't mention of a "universal reference" in the draft of the standard I'm using. AFAIK Scott Meyers invented the term.Alfilaria
Thanks, I fixed the check is_integral_lvalue and used your solution - it works as expected, just what I needed. Concerning the universal reference: I think unpack_int_tuple will forward the tuple to actual unpacking function, that's why I used it. It actually does not matter, if the tuple is lvalue or rvalue. But it's fields must be lvalues.Vicar
@MikhailMaltsev get familiar with the implementation of std::forward and std::moveCorinnecorinth
@MikhailMaltsev The tuple is being taken as an rvalue reference, not a frowarding/universal reference. If you want a forwarding reference, you'll need to take the entire type as a template parameter and then extract the contained types separately.Serif
How a solution with fold expressions would look like?Tank
@GermánDiago template<bool... values> struct all_of : std::integral_constant<bool, (values && ...)> {};; or a simple constexpr function template along those lines.Serif
I'm familiar with move and forward semantics. I just kind of "forgotten" that the tuple itself is not a template parameter here.Vicar
@remyabel doesn't really change anything, how you call something it's not that relevant as long as everyone knows what the thing in question is. For what it's worth, the C++ committee didn't gave a proper name to the thing either .Corinnecorinth
T
0

The non-MPL version of and_ is proposed here, now renamed to conjunction.

The implementation was in libstdc++ (as std::__and_ in <type_traits>) long ago.

Here is my simpler implementation (with less explicit specializations).

Trotman answered 3/3, 2016 at 7:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.