Home > Back-end >  How to build a type generator class based on the input data type and container type(by template argu
How to build a type generator class based on the input data type and container type(by template argu

Time:08-04

There exists two basic data types in my tiny demo program, represented by the below classes:

struct FloatDataTypeDescriptor {
  using dtype = float;
};
struct Uint8DataTypeDescriptor {
  using dtype = uint8_t;
  uint8_t zero_point_;
  float scale_;
};

Conceptually, the data type descriptor and data actual holder(might be std::array, std::unique_ptr, std::vector...) are tightly couple together, So i decided to use std::pair to represent the data chunk, like:

using ChunkTypeA = std::pair<FloatDataTypeDescriptor, std::vector<FloatDataTypeDescriptor::dtype>>;
using ChunkTypeB = std::pair<Uint8DataTypeDescriptor, std::vector<Uint8DataTypeDescriptor::dtype>>;
using ChunkTypeC = std::pair<FloatDataTypeDescriptor, std::unique_ptr<FloatDataTypeDescriptor::dtype[]>;
// ...

This can work though, but writing such template alias all over the place is a little bit of tedious. So i've thought of using partial specialization to create a "type generator", produce the needed std::pair<> type by provided templates argument.

// primary template
template <typename TypeDescriptor, template<typename, typename...> class Container>
struct PairedTypeGenerator;

// partial specialization for std::vector
template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>> {
  using type = std::pair<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>>;
};

And use it like:

using a = PairedTypeGenerator<Uint8TypeDescriptor, std::vector>::type;

I've tried to use variadic template pack in the template template parameter Container. Since some Container might need extra argument other than the data type(like vector Allocator / unique_ptr Deleter). It didn't work, clang tolds me:

<source>:21:53: error: template argument for template template parameter must be a class template or type alias template
struct PairedTypeGenerator<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>> {

Thanks to @463035818_is_not_a_number liberal and great advice, i continue to add more specialization for std::vector / std::unique_ptr / std::array

template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::vector> {
  using type = std::pair<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>>;
};

template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::unique_ptr> {
  using type = std::pair<TypeDescriptor, std::unique_ptr<typename TypeDescriptor::dtype[]>>;
};


template <typename TypeDescriptor, typename Deleter>
struct PairedTypeGenerator<TypeDescriptor, std::unique_ptr, Deleter> {
  using type = std::pair<TypeDescriptor, std::unique_ptr<typename TypeDescriptor::dtype[], Deleter>>;
};

So now i can support case that std::unique_ptr with custom Deleter, just use it as:

using Ptr = EmbeddingPairedTypeGenerator<Uint8EmbeddingDataTypeDescriptor, std::unique_ptr, decltype(&std::free)>::type;

However, for std::array, things become much tricker, the std::array Container type need a non type template parameter, which cannot be matched by the parameter pack. I just want to use it by some syntax similar as:

using Array = PairedTypeGenerator<Uint8DataTypeDescriptor, std::array, 512>;

CodePudding user response:

std::vector<typename TypeDescriptor::dtype> is not a template. Its a type. In the specialization you want to specialize for std::vector not for a particular instantiation of it. The error is due to the primary template expecting a template as second parameter rather than a type.

#include <vector>

struct FloatDataTypeDescriptor {
  using dtype = float;
};
struct Uint8TypeDescriptor {
  using dtype = unsigned;
};

// primary template
template <typename TypeDescriptor, template<typename, typename...> class Container>
struct PairedTypeGenerator;

// partial specialization for std::vector
template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::vector> {
                                    //        ^^^------------------------
  using type = std::pair<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>>;
};

using a = PairedTypeGenerator<Uint8TypeDescriptor, std::vector>::type;

However, you only need a single alias template, no specializations:

template <typename T,template <typename,typename...> class Container> 
using PairedType = std::pair<T,Container<typename T::dtype>>;

using a = PairedType<Uint8TypeDescriptor,std::vector>;

CodePudding user response:

It is possible if you're willing to accept helpers/wrappers.

Here's my take on this:

template <auto X> 
struct Constant { 
    static constexpr auto value = X; 
};

template <typename T, typename U>
using array_helper = std::array<T, U::value>;

// base
template <typename TypeDescriptor, template<typename, typename...> class Container, typename... Args>
struct PairedTypeGenerator {
  using type = std::pair<TypeDescriptor, Container<typename TypeDescriptor::dtype, Args...>>;
};

// std::array
template <typename TypeDescriptor, typename U>
struct PairedTypeGenerator<TypeDescriptor, array_helper, U> {
    using type = std::pair<TypeDescriptor, array_helper<typename TypeDescriptor::dtype, U>>;
};

using a = PairedTypeGenerator<Uint8TypeDescriptor, array_helper, Constant<3>>::type;

Full example:

#include <vector>
#include <array>
#include <memory>

struct FloatDataTypeDescriptor {
  using dtype = float;
};
struct Uint8TypeDescriptor {
  using dtype = unsigned;
};

template <auto X> 
struct Constant { 
    static constexpr auto value = X; 
};

template <typename T, typename U>
using array_helper = std::array<T, U::value>;

// base
template <typename TypeDescriptor, template<typename, typename...> class Container, typename... Args>
struct PairedTypeGenerator {
  using type = std::pair<TypeDescriptor, Container<typename TypeDescriptor::dtype, Args...>>;
};

// std::array
template <typename TypeDescriptor, typename U>
struct PairedTypeGenerator<TypeDescriptor, array_helper, U> {
    using type = std::pair<TypeDescriptor, array_helper<typename TypeDescriptor::dtype, U>>;
};

template <typename T>
class AllocNew : public std::allocator<T>
{};

template <typename T>
class Deleter : public std::default_delete<T>
{};

int main()
{
    using a = PairedTypeGenerator<Uint8TypeDescriptor, array_helper, Constant<3>>::type;

    using u = PairedTypeGenerator<Uint8TypeDescriptor, std::unique_ptr>::type;
    using u2 = PairedTypeGenerator<Uint8TypeDescriptor, std::unique_ptr, Deleter<unsigned>>::type;

    using v = PairedTypeGenerator<Uint8TypeDescriptor, std::vector>::type;
    using v2 = PairedTypeGenerator<Uint8TypeDescriptor, std::vector, AllocNew<unsigned>>::type;

    // array
    static_assert(std::is_same_v<a, std::pair<Uint8TypeDescriptor, std::array<unsigned, 3>>>);
    
    // vector
    static_assert(std::is_same_v<v, std::pair<Uint8TypeDescriptor, std::vector<unsigned>>>);
    static_assert(std::is_same_v<v2, std::pair<Uint8TypeDescriptor, std::vector<unsigned, AllocNew<unsigned>>>>);
    
    // unique_ptr
    static_assert(std::is_same_v<u, std::pair<Uint8TypeDescriptor, std::unique_ptr<unsigned>>>);
    static_assert(std::is_same_v<u2, std::pair<Uint8TypeDescriptor, std::unique_ptr<unsigned, Deleter<unsigned>>>>);
}

clang1400 -std=c 17

  • Related