Nikos C.
Nikos C.

Reputation: 51870

Constructing a tuple from values returned by member functions of objects inside another tuple

(This could be an XY Problem, so I'm providing some background information prior to the actual question.)

Background

I currently have a function (not a template) that computes different hash types (CRC32, MD5, SHA1, etc.) The data comes from a provider that can only provide a pointer to a chunk of the data at a time. The function computes the hashes on chunks of data iteratively.

Advancing to the next chunk is a very costly operation (involves decompression) and it can only go forward. Also the architecture must be kept zero-copy. As a result, all the selected hashes must be computed at once while iterating on the same chunks of data. Hash type selection is done through bool parameters:

std::tuple<uint32_t, QByteArray, QByteArray, QByteArray>
computeHashes(DataProvider& data, bool do_crc, bool do_md5, bool do_sha1,
              bool do_sha256);

If one of the flags is false, the caller ignores the corresponding empty tuple element.

Actual Question

I am very unhappy with the above API. So I decided to write a cleaner looking function template. No boolean switches and no dummy tuple elements in the return value:

auto [crc, sha256] = computeHashes<Hash::CRC32, Hash::MD5>(data_provider);

I got the code mostly working, except for the last step where I need to actually return the results. This is trimmed down from the real code, and with only two hash functions in order to keep the example as short as possible:

enum class Hash { CRC32, MD5 };

template <HashType> struct Hasher
{};

template<> struct Hasher<HashType::CRC32>
{
    void addData(const char* data, int len);
    uint32_t result() const;
};

template<> struct Hasher<HashType::MD5>
{
    void addData(const char* data, int len);
    QByteArray result() const;
};

template <HashType... hash_types>
auto computeHashes(DataProvider& provider)
{
    std::tuple<Hasher<hash_types>...> hashers;

    while (provider.hasMoreChunks()) {
        auto [chunk, len] = provider.nextChunk();
        std::apply([chunk, len](auto&... hasher)
                       { (..., hasher.addData(chunk, len); },
                   hashers);
    }
    return std::make_tuple( ??? );
}

I'm stuck at the last step: how do I return each result? A hard-coded return would look this:

return std::make_tuple(res, std::get<0>(hashers).result(),
                       std::get<1>(hashers).result());

This isn't suitable of course. How do I do this?

Upvotes: 2

Views: 103

Answers (1)

Sopel
Sopel

Reputation: 1219

since std::apply forwards returned values by decltype(auto) you can just construct a tuple with std::apply and return it. This can be coalesced with your transformations into one call.

template <HashType... hash_types>
static auto computeHashes(DataProvider& provider)
{
    return std::apply(
        [&provider](auto&&... hashers) { 
            while (provider.hasMoreChunks()) 
            {
                auto [chunk, len] = provider.nextChunk();
                (..., hashers.addData(chunk, len));
            }

            return std::make_tuple(std::move(hashers.result())...);
        },
        std::tuple<Hasher<hash_types>...>{}
    );
}

Upvotes: 4

Related Questions