Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing static version of std::all_of using template metaprogramming?

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).

like image 995
Mikhail Maltsev Avatar asked Dec 01 '14 03:12

Mikhail Maltsev


1 Answers

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...>>{};
like image 80
T.C. Avatar answered Nov 06 '22 11:11

T.C.