Вызов std :: function со списком типов вариантов - PullRequest
2 голосов
/ 08 мая 2019

Я работаю со сторонней библиотекой, которая передает значения с использованием пользовательских векторов и типов вариантов. Эти векторы, однако, соответствуют 1: 1 моим функциям. Моя конечная цель - уменьшить количество шаблонов.

Придуманный пример:

#include <functional>

// A third party type I can't change
struct dumb_varient_type
{
    int to_int() { return 3; }
    double to_double() { return 3.14; }
    std::string to_string() { return "Pi"; }
};

template< class R, class... Args >
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
    // call func, with the correct types
    return func( vector_of_pain_to_expanded_parameter_pack_or_something(args) );
}


int main(int argc, char **argv) {

    std::vector<dumb_varient_type> arg1 = get_args_from_magic();
    auto c1 = [](int, double) {/*do work*/};
    auto c2 = [](double, std::string) {/*do work*/};

    auto val1 = do_callback( c1, arg1 );
    auto val2 = do_callback( c2, arg1 );

    // std::vector< dumb_varient_type > arg2( /* Anything*/ );
    // std::function<int(/*Anything*/)> c3;
    // auto val3 = do_callback( c3, arg2 );
}

Мне не нужно беспокоиться о двусмысленности; Список аргументов не нужно направлять или отправлять в правильную функцию. Я знаю предполагаемый обратный звонок. Но так как список аргументов создается во время выполнения, теоретически может быть несоответствие в числе аргументов или типе.

Как можно реализовать do_callback (или эквивалент)

Ответы [ 2 ]

3 голосов
/ 08 мая 2019
// A third party type I can't change
struct dumb_varient_type {
  int to_int() { return 3; }
  double to_double() { return 3.14; }
  std::string to_string() { return "Pi"; }
};

Напишите вспомогательный тег:

template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};

Затем в пространство имен tag_t или dumb_variant_type запишите эти перегрузки: (примечание: не шаблоны, только перегрузки)

int to_type( tag_t<int>, dumb_variant_type const& dumb ) { return dumb.to_int(); }
double to_type( tag_t<double>, dumb_variant_type const& dumb ) { return dumb.to_double(); }

и т. Д.Это делает преобразование вашего тупого варианта более единообразным в общий код.

Теперь мы напишем индексатор:

template<std::size_t...Is>
auto indexer(std::index_sequence<Is...>){
  return [](auto&&f){ return f(std::integral_constant<std::size_t,Is>{}... ); };
}
template<std::size_t N>
auto indexer_upto(std::integral_constant<std::size_t,N> ={}){
  return indexer(std::make_index_sequence<N>{});
}

Этот помощник избавляет от необходимости разбивать вашу функцию на части.Это дает нам доступ к распакованному пакету индексов по желанию.

При вызове индексатора возвращается лямбда, которая, в свою очередь, принимает лямбду, предоставленную клиентом.Клиентская лямбда вызывается с распакованным пакетом параметров времени компиляции.

Используя это, do_callback короток и приятен:

template< class R, class... Args >
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
  if(sizeof...(Args) > args.size()) throw std::invalid_argument(); // or whatever
  return indexer_upto< sizeof...(Args) >()( []( auto...Is ){
    return func( to_type(tag<Args>, args[Is]) ... );
  });
}

и готово.Пожалуйста, извините за любые tpyos.

2 голосов
/ 08 мая 2019

Вам нужен какой-то способ неявного преобразования dumb_varient_type в правильный тип функции. Затем вам нужен способ распаковать векторные аргументы в сам вызов функции. Последнее невероятно опасно, поскольку вы не знаете размер вектора во время компиляции и, следовательно, не можете гарантировать, что всегда будет достаточно элементов, соответствующих количеству параметров в функции.

Простой и наивный подход для первого шага - создать класс-оболочку с правильными операторами преобразования, а затем преобразовать вектор dumb_varient_type в smarter_dumb_variant_type.

struct smarter_dumb_variant_type {

    smarter_dumb_variant_type(dumb_varient_type& dvt)
      : value(dvt)
    {   }

    operator ()(int) {
      return value.to_int();
    }

    //... rest of the operators

    dumb_varient_type& value;
  };

  std::vector<smarter_dumb_variant_type> Convert(std::vector<dumb_varient_type>&) {
    //Implerment... 
  }

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

Решение по распаковке вектора можно найти в этом вопросе здесь (но используйте smarter_dumb_variant_type вместо int)

Используя это, ваш do_callback будет выглядеть примерно так:

template< class R, class... Args, size_t num_args>
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
   unpack_caller<num_args> caller;
   return caller(func, Convert(args));
}

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

   if (args.size() < num_args) {
     throw;
   }
...