Преобразование между различными типами типов образцов

Я хочу использовать шаблон для преобразования между различными типами образцов

template<class T,class U>
void convert(const T* source, U* dest, size_t n)
    {
    do
        {
        double G=double(max(*dest))/max(*source);
        T diff=max(*source) - min(*source);
        *dest=U(makeUnsigned(*source - min(*source))*G/makeUnsigned(diff)
              +makeUnsigned(max(*source) - *source)*double(min(*dest))/makeUnsigned(diff));

        ++dest;
        ++source;
        --n;
        }
    while(n!=0);
    }

Теперь мне нужна автоматически сгенерированная матрица, чтобы я мог

convert[from][to](source,dest,n);

где каждый элемент относится к правильной версии. Я знаю, что здесь я должен использовать указатели функций (каждый элемент должен быть указателем на функцию, принимающую const void*, void* size_t, что в любом случае эквивалентно).

Я могу это сделать?


person user877329    schedule 07.04.2013    source источник
comment
Я не понимаю. convert — функция; что convert[from][to] должен делать? Или вы имеете в виду convert<from, to>?   -  person Joseph Mansfield    schedule 07.04.2013
comment
@sftrabbit Автоматически сгенерированная матрица вызываемых элементов, поэтому мне не нужны MxN случаев во вложенных переключателях.   -  person user877329    schedule 07.04.2013


Ответы (1)


Сначала напишите это:

template<typename T, typename U>
U convert( T const& src );

потому что все остальное должно быть написано с метапрограммированием шаблона. Хорошая вещь в этом заключается в том, что вы можете специализировать свои convert<int, double> и convert<std::string, int>, если вам это нужно.

Далее напишите это:

template<typename T, typename U>
void convert_buffer( T const* src, U* dest, size_t n );

который просто вызывает convert выше в цикле. Я разделил это на две функции, потому что, возможно, неоднородный код находится в convert, а вызов функции с видимым телом практически не требует дополнительных затрат.

А потом напишите это:

typedef void(*blind_converter)(void const*, void*, size_t);
template<typename T, typename U>
void convert_blind_buffer( void const* src, void* dest, size_t n ) {
  return convert_buffer( reinterpret_cast<T const*>(src), reinterpret_cast<U*>(dest), n );
}

который инкапсулирует приведение и не требует от вас выполнения теоретически недопустимого приведения типа указателя. Это "слепое преобразование" буфера -- слепое, поскольку оно занимает void*с.

Далее, мы не хотим поддерживать ваш массив NxN вручную. Тип для хранения упорядоченного списка типов:

template<typename... Ts>
struct type_list {};

А затем мы пишем метапрограмму для построения массива NxN:

template<typename Src, typename DestList>
struct make_convert_one_way;
template<typename Src, typename... Ds>
struct make_convert_one_way< Src, type_list<Ds...> > {
  std::array< blind_converter, sizeof...(Ds) > operator()() const {
    return { convert_blind_buffer< Src, Ds >... };
  }
};

template<typename list>
struct make_convert_array;

template<typename... Ts>
struct make_convert_array< type_list<Ts...> > {
  std::array< std::array<blind_converter, sizeof...(Ts) >, sizeof...(Ts) > operator()() const {
    return { make_convert_one_way< Ts, type_list<Ts...> >... };
  }
};

typedef type_list< int, char, double > my_list;
auto convert_array = make_convert_array<my_list>()();

Или что-то вдоль этих линий.

Если ваши исходный и конечный типы не являются однородными, приведенное выше нужно будет изменить, чтобы принять два type_list, но в этом нет ничего принципиально сложного.

Следующей полезной вещью будет возможность сопоставления типа с индексом в приведенном выше массиве, потому что поддержание этого должно быть работой компилятора.

template<typename T, typename List, typename=void>
struct index_of;
template<typename T, typename T0, typename... Ts>
struct index_of<T, type_list<T0, Ts...>, typename std::enable_if<
  std::is_same<T, T0>::value
>::type >: std::integral_constant< std::size_t, 0 > {};
template<typename T, typename T0, typename... Ts>
struct index_of<T, type_list<T0, Ts...>, typename std::enable_if<
  !std::is_same<T, T0>::value
>::type >: std::integral_constant< std::size_t, index_of<T, type_list<Ts...>::value+1 > {};

который позволяет вам сделать это:

static_assert( index_of< int, my_list >::value == 0, "all is well!" );

Теперь, вы можете захотеть обернуть это каким-нибудь стилем. Стратегия может выглядеть так:

template<typename List>
struct EnumDressing;
template<typename... Ts>
struct EnumDressing<type_list<Ts...>> {
  enum type {
    e_begin = 0,
    e_end = sizeof...(Ts),
  };
  template<typename T>
  static constexpr type value() {
    return static_cast<type>( index_of<T, type_list<Ts...> >::value );
  }
};

где у нас есть EnumDressing<my_list>::type как тип enum, который представляет целочисленные имена для ваших типов, и его значения можно получить через EnumDressing<my_list>::value<int>(). Естественно, вы очищаете это с помощью typedefs:

typedef EnumDressing<my_list> Types;
typedef Types::type eType;

struct typed_array {
  eType type;
  void* buff;
  size_t n;
};
void do_convert( typed_array src, typed_array dst) {
  Assert(src.n == dst.n);
  convert_array[ src.type ][ dst.type ]( src.buff, dst.buff, std::min( src.n, dst.n ) );
}
template<typename T, size_t N>
typed_array make_typed_array( T (&arr)[N] ) {
  return { Types::value<T>(), reinterpret_cast<void*>( &arr[0] ), N };
}

int main() {
  double d[100];
  int i[100];
  do_convert( make_typed_array( d ), make_typed_array( i ) );
}

с естественно реальными вариантами использования, отделяющими создание typed_array от его использования.

person Yakk - Adam Nevraumont    schedule 07.04.2013