UPDATE: Be careful when using this code for model storage or attribute binding. Models must be stroed with proper alignment, but my container have none.
Ok, here is my implementation.
It's inline&template-only, so you should put it in a header.
Keep in mind that it expects that locations of vertex attribs are specified in a shader with layout(location = ..) and start with 0 (then 1,2,3...).
Features:
Attribute setup:
SetVertexAttribs<AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>>();
// It enables attrib arrays (here: 0, 1 and 2), and then sets attrib pointers for them.
// It also selects correct glVertexAttrib{|I|L}Pointer function depending on type of each parametr.
Class Vec:
Vec<float, 4> a {1, 2, 3, 4}, b;
// First parametr can be any type. Second parametr is
// number of objects of that type in the vector.
// First parametr can be any default scalar type.
// Second parametr can only be 1, 2, 3 or 4.
a.x = 2; // you can use .x or .r - they have same meaning
a.y = 3; // you can use .y or .g - they have same meaning
a.z = 4; // you can use .z or .b - they have same meaning
a.w = 4; // you can use .w or .a - they have same meaning
a = a + a - a + -a; // Overloaded operators
a = {1, 2, 3, 4};  b = a;  // Overloaded assignment
a += {0, 1, 2, 3}; b += a;
a -= {0, 1, 0, 1}; b -= a;
Vec<float, 4>::type var; // here means `float var;`
// Vec<...>::type means type of a single element
std::cout << a.dim; // here prints 4  -- number of dimensions of a vector
Class AttribLayout:
Arrays of this type are useful for storing models.
AttribLayout<Vec<float, 4>, Vec<int, 2>, Vec<double, 3>>
    obj1, obj2({0,0,0,0},{1,2},{10,20,30}); // Constructors
// This class uses trick with char array - don't worry about padding, it does not have it.
std::cout << obj1.elements; // Here prints 3  -- number of vectors in layout
std::cout << obj1.byte_len; // Same as `sizeof obj1`
Vec<int, 2> var = obj1.get<1>(); // Returns vector with a specific number in a layout
double dvar = obj2.get<2,1>; // Here dvar == 20. First parametr means number of vector in a layout, second parametr means number of element in a vector.
obj1.set<1>({1,2}); // Parametrs mean same things as in `get()`.
obj1.set<2,0>(0.0); // I think you understood what `set()` does.
AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>::type_at<1> var;
// Here means `Vec<int, 2> var;`
Useful functions:
type2glconst<int>::value // == GL_INT
// I think you understand what it's for.
type2glattribfunc<float> // same as glVertexAttribPointer
type2glattribfunc<double> // same as glVertexAttribLPointer
type2glattribfunc</*any integer type*/> // same as glVertexAttribIPointer
Code:
    template <typename T>
    struct type2glconst
    {
        static_assert(std::is_same<T, void>::value, "Invalid type!");
        static constexpr GLenum value = 0;
    };
    template <> struct type2glconst<unsigned char>  {static constexpr GLenum value = GL_UNSIGNED_BYTE;};
    template <> struct type2glconst<signed char>    {static constexpr GLenum value = GL_BYTE;};
    template <> struct type2glconst<char>           {static constexpr GLenum value = Utils::is_char_signed ? GL_BYTE : GL_UNSIGNED_BYTE;};
    template <> struct type2glconst<unsigned short> {static constexpr GLenum value = GL_UNSIGNED_SHORT;};
    template <> struct type2glconst<signed short>   {static constexpr GLenum value = GL_SHORT;};
    template <> struct type2glconst<unsigned int>   {static constexpr GLenum value = GL_UNSIGNED_INT;};
    template <> struct type2glconst<signed int>     {static constexpr GLenum value = GL_INT;};
    template <> struct type2glconst<float>          {static constexpr GLenum value = GL_FLOAT;};
    template <> struct type2glconst<double>         {static constexpr GLenum value = GL_DOUBLE;};
    template <typename T>
    inline void type2glattribfunc(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
    {
        static_assert(type2glconst<T>::value, "Invalid type!");
        glVertexAttribIPointer(index, size, type2glconst<T>::value, stride, pointer);
    }
    template <>
    inline void type2glattribfunc<float>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
    {
        glVertexAttribPointer(index, size, type2glconst<float>::value, GL_FALSE, stride, pointer);
    }
    template <>
    inline void type2glattribfunc<double>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
    {
        glVertexAttribLPointer(index, size, type2glconst<double>::value, stride, pointer);
    }
    template <typename T, unsigned int D>
    struct Vec
    {
        static_assert((std::is_void<T>::value || type2glconst<T>::value) && D <= 4, "Invalid dimension for vector!");
        static constexpr int dim = 0;
        using type = void;
    };
    template <typename T>
    struct Vec<T, 1>
    {
        using type = T;
        static constexpr int dim = 1;
        union {T x, r;};
        Vec<T, 1> operator+(const Vec<T, 1> &o) const {return {x + o.x};}
        Vec<T, 1> &operator+=(const Vec<T, 1> &o) {x += o.x; return *this;}
        Vec<T, 1> operator-() const {return {-x};}
        Vec<T, 1> operator-(const Vec<T, 1> &o) const {return {x - o.x};}
        Vec<T, 1> &operator-=(const Vec<T, 1> &o) {x -= o.x; return *this;}
    };
    template <typename T>
    struct Vec<T, 2>
    {
        using type = T;
        static constexpr int dim = 2;
        union {T x, r;};
        union {T y, g;};
        Vec<T, 2> operator+(const Vec<T, 2> &o) const {return {x + o.x, y + o.y};}
        Vec<T, 2> &operator+=(const Vec<T, 2> &o) {x += o.x; y += o.y; return *this;}
        Vec<T, 2> operator-() const {return {-x, -y};}
        Vec<T, 2> operator-(const Vec<T, 2> &o) const {return {x - o.x, y - o.y};}
        Vec<T, 2> &operator-=(const Vec<T, 2> &o) {x -= o.x; y -= o.y; return *this;}
    };
    template <typename T>
    struct Vec<T, 3>
    {
        using type = T;
        static constexpr int dim = 3;
        union {T x, r;};
        union {T y, g;};
        union {T z, b;};
        Vec<T, 3> operator+(const Vec<T, 3> &o) const {return {x + o.x, y + o.y, z + o.z};}
        Vec<T, 3> &operator+=(const Vec<T, 3> &o) {x += o.x; y += o.y; z += o.z; return *this;}
        Vec<T, 3> operator-() const {return {-x, -y, -z};}
        Vec<T, 3> operator-(const Vec<T, 3> &o) const {return {x - o.x, y - o.y, z - o.z};}
        Vec<T, 3> &operator-=(const Vec<T, 3> &o) {x -= o.x; y -= o.y; z -= o.z; return *this;}
        Vec<T, 3> operator*(const Vec<T, 3> &o) const {return {y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x};}
        Vec<T, 3> &operator*=(const Vec<T, 3> &o) {*this = *this * o; return *this;}
    };
    template <typename T>
    struct Vec<T, 4>
    {
        using type = T;
        static constexpr int dim = 4;
        union {T x, r;};
        union {T y, g;};
        union {T z, b;};
        union {T w, a;};
        Vec<T, 4> operator+(const Vec<T, 4> &o) const {return {x + o.x, y + o.y, z + o.z, w + o.w};}
        Vec<T, 4> &operator+=(const Vec<T, 4> &o) {x += o.x; y += o.y; z += o.z; w += o.w; return *this;}
        Vec<T, 4> operator-() const {return {-x, -y, -z, -w};}
        Vec<T, 4> operator-(const Vec<T, 4> &o) const {return {x - o.x, y - o.y, z - o.z, w - o.w};}
        Vec<T, 4> &operator-=(const Vec<T, 4> &o) {x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this;}
    };
    template <typename T, typename ...P>
    struct AttribLayout_InternalValue
    {
        static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
        using curtype = T;
    };
    template <typename T, typename ...P>
    struct AttribLayout_InternalContainer
    {
        static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
        using curtype = T;
        using nexttype = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
    };
    template <typename ...P>
    class AttribLayout
    {
    protected:
        static_assert(sizeof...(P) > 0, "Zero-length attrib layout!");
        using cont_type = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
    public:
        static constexpr int elements = sizeof...(P);
    protected:
        template <unsigned int N, typename T> struct type_at_internal
        {
            using type = typename type_at_internal<(N - 1), typename T::nexttype>::type;
        };
        template <typename T> struct type_at_internal<0, T>
        {
            using type = T;
        };
        template <unsigned int N> using cont_type_at = typename type_at_internal<N, cont_type>::type;
    public:
        template <unsigned int N> using type_at = typename type_at_internal<N, cont_type>::type::curtype;
        template <unsigned int N, typename T> struct bytes_internal
        {
            static constexpr unsigned int var = bytes_internal<(N - 1), T>::var + (type_at<(N - 1)>::dim * sizeof(typename type_at<(N - 1)>::type));
        };
        template <typename T> struct bytes_internal<0, T>
        {
            static constexpr unsigned int var = 0;
        };
        static constexpr unsigned int byte_len = bytes_internal<(sizeof...(P)), void>::var;
        unsigned char bytearr[byte_len];
        template <unsigned int N> type_at<N> get()
        {
            static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
            type_at<N> ret;
            std::memcpy(&ret, bytearr + bytes_internal<N, void>::var, sizeof (type_at<N>));
            return ret;
        }
        template <unsigned int N> void set(const type_at<N> &ref)
        {
            static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
            std::memcpy(bytearr + bytes_internal<N, void>::var, &ref, sizeof (type_at<N>));
        }
        template <unsigned int N, unsigned int X> typename type_at<N>::type get()
        {
            static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
            static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
            typename type_at<N>::type ret;
            std::memcpy(&ret, bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), sizeof (typename type_at<N>::type));
            return ret;
        }
        template <unsigned int N, unsigned int X> void set(typename type_at<N>::type obj)
        {
            static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
            static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
            std::memcpy(bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), &obj, sizeof (typename type_at<N>::type));
        }
    protected:
        template <unsigned int N, unsigned int M> struct ctor_internal
        {
            static void func(void **ptr, unsigned char *arr)
            {
                std::memcpy(arr + bytes_internal<(M - N), void>::var, *ptr, sizeof (type_at<(M - N)>));
                ctor_internal<(N - 1), M>::func(ptr + 1, arr);
            }
        };
        template <unsigned int M> struct ctor_internal<0, M>
        {
            static void func(void **ptr, unsigned char *arr)
            {
                std::memcpy(arr + bytes_internal<M, void>::var, *ptr, sizeof (type_at<M>));
            }
        };
    public:
        AttribLayout()
        {
            static_assert(sizeof (decltype(*this)) == byte_len, "Crappy compiler have added padding to AttribLayout!");
            static_assert(alignof (decltype(*this)) == 1, "Crappy compiler have added alignment to AttribLayout!");
        }
        AttribLayout(P ...params)
        {
            void *ptrs[sizeof...(P)] {(void *)¶ms...};
            ctor_internal<(sizeof...(P) - 1), (sizeof...(P) - 1)>::func(ptrs, bytearr);
        }
    };
    namespace InternalStuff
    {
        template <class T, unsigned N> struct SetVertexAttribs_internal
        {
            static void func()
            {
                SetVertexAttribs_internal<T, (N - 1)>::func();
                glEnableVertexAttribArray(N);
                type2glattribfunc<typename T::template type_at<N>::type>(N, T::template type_at<N>::dim, T::byte_len, (void *)T::template bytes_internal<N, void>::var);
            }
        };
        template <class T> struct SetVertexAttribs_internal<T, 0>
        {
            static void func()
            {
                glEnableVertexAttribArray(0);
                type2glattribfunc<typename T::template type_at<0>::type>(0, T::template type_at<0>::dim, T::byte_len, (void *)T::template bytes_internal<0, void>::var);
            }
        };
    }
    template <class T>
    void SetVertexAttribs()
    {
        InternalStuff::SetVertexAttribs_internal<T, (T::elements - 1)>::func();
    }