vendredi 27 mars 2015

Match a class by parameter type in a c++ template-generated class hierarchy

Intro


I am working on a custom memory allocator and need to add some bookkeeping info to the header of each allocated chunk. There are several different chunk types and the bookkeeping info differs as well. For instance, for chunks shared between threads there is a need to add a reference counter, for chunks used by single thread there is no such need. For chunks taken from a memory pool there is a need to keep a reference to the originating pool, for chunks taken from the free store there is no such need.


Problem


So I would like to have a generic interface to add and get certain data types for a given chunk layout. Experimenting with this idea I came to a solution that is similar to std::tuple. However unlike tuples every type I add to the header is going to be unique. I just started to learn template meta-programming and other intricacies of c++, however the part with adding a type was straightforward for me.


The problem I came across is implementing a method similar to C++14 std::get by type template function for tuples. I figured there is no need to write much code for this as the compiler is able to match the correct base class in a method call. At first I put the get method right into template-generated layout class. However the compiler fails to match the correct class in this case. The problem was solved by moving the get method to another, manually added, level of class hierarchy.


The code below demonstrates the problem. Defining HAVE_GET_IN_LAYOUT to 0 produces a working solution while defining it to 1 produces a broken solution.


The question is, what is broken in this case?



#include <cstddef>
#include <iostream>

#define HAVE_GET_IN_LAYOUT 0

constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return (size < 0x8
? (offset + 0x3) & ~0x3
: (size < 0x10 ? (offset + 0x7) & ~0x7 : (offset + 0xf) & ~0xf));
}

template <std::size_t Start, typename... Ts> struct Layout {
static constexpr std::size_t Size = 0;
static constexpr std::size_t Offset = Start;
static constexpr std::size_t TotalSize = Start;
};

template <std::size_t Start, typename T, typename... Ts>
struct Layout<Start, T, Ts...>
: public Layout<Align(sizeof(T), Start) + sizeof(T), Ts...> {
using Type = T;

static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, Start);
static constexpr std::size_t TotalSize = Layout<Offset + Size, Ts...>::TotalSize;

Type value = Offset - Start; // no particular meaning, just for testing.

#if HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }

template <typename U> U &get() { return helper<U>(this); }
#endif
};

template <typename... Ts> struct Result : public Layout<0, Ts...> {
#if !HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }

template <typename U> U &get() { return helper<U>(this); }
#endif
};

int main() {
std::cout << "layout size <> = " << Layout<0>::TotalSize << std::endl;
std::cout << "layout size <int> = " << Layout<0, int>::TotalSize << std::endl;
std::cout << "layout size <long> = " << Layout<0, long>::TotalSize << std::endl;
std::cout << "layout size <int,int> = " << Layout<0, int, int>::TotalSize << std::endl;
std::cout << "layout size <int,long> = " << Layout<0, int, long>::TotalSize << std::endl;
std::cout << "layout size <long,int> = " << Layout<0, long, int>::TotalSize << std::endl;
std::cout << "layout size <long,long> = " << Layout<0, long, long>::TotalSize << std::endl;

std::cout << "get: " << Result<int, long, long double>{}.get<long>() << std::endl;

return 0;
}

Aucun commentaire:

Enregistrer un commentaire