c++templatesc++14packed

Creating a packed version of a template class in C++14


I would like to make an __attribute__((packed)) version of a template class, to use it in packed structs to send or receive as bytes. But I don't want the cost of packed to impact the arithmetic and other aspects on the regular use of the class (compiling for ARM using GCC with c++14).

So I have a template class like this example:

template<typename T>
struct Vec3 {
    T x, y, z;

    constexpr Vec3() = default;
    constexpr Vec3(T x, T y, T z) : x(x), y(y), z(z) {}

    constexpr Vec3& operator=(const Vec3& v);

    constexpr Vec3 operator+(const Vec3& v) const;

    template<typename U>
    constexpr auto operator+(const Vec3<U>& v) const -> Vec3<decltype(T{} + U{})>;

    // many other member functions
}

template<typename T, typename U>
constexpr auto dot(const Vec3<T>& v1, const Vec3<U>& v2) -> decltype(T{} * U{});

// other non-member functions

Now I would like to make a packed version of the Vec3 class, something like:

#include <type_traits>

template<typename T>
class PackedType;

template<typename T>
using Packed = std::conditional_t<std::is_arithmetic<T>::value, T, PackedType<T>>;

template<typename T>
struct __attribute__((packed)) PackedType<Vec3<T>> {
    Packed<T> x, y, z; // make sure the internal type is packable

    constexpr PackedType(const Vec3<T>& v) : x(v.x), y(v.y), z(v.z) {}

    constexpr operator Vec3<T>() const { return Vec3<T>(T(x), T(y), T(z)); }
}

But this is where it breaks down. The call to function expecting Vec3 use template argument deduction/substitution, but PackedType<Vec3<T>> isn't a Vec3, nor is it derived from Vec3. So the compiler cannot deduce anything.

We would like the packed Vec3 class to be converted to a regular Vec3 when used in functions. As I said previously, we don't want/need operation to be performed on the packed values, so making a copy is fine.

But now we need to provide overload for all functions to use the packed version of the class, plus all the (regular, packed), (packed, regular), (packed, packed) for function with two arguments and so on... And I don't mean just the Vec3.h/cpp file, any function that as a templated Vec3<T> as argument might need to be overloaded.

As for the other options:

To be clear, what I would like is a way to convert automatically a packed class into a regular class, the same way a c array decay into a pointer for example.


Solution

  • The answer suggested in a comment does the trick.

    #include <type_traits>
    
    template<typename T>
    struct Vec3 {
        using type = T;
        T x, y, z;
        //...
    }
    
    template<typename T>
    struct isVec3Conv : std::false_type {};
    
    template<typename T>
    struct isVec3Conv<Vec3<T>> : std::true_type {};
    
    template<typename T>
    struct isVec3Conv<PackedType<Vec3<T>>> : std::true_type {};
    
    
    template<typename V1, typename V2,
        std::enable_if_t<isVec3Conv<V1>::value && isVec3Conv<V2>::value, bool> = 0>
    constexpr auto dot(const V1& v1, const V2& v2) ->
        decltype(typename V1::type{} * typename V2::type{});
    

    In that case we can replace typename V1::type{} by v1.x, but my classes have more template type parameters and so my implementation become a bit longer because of that.