Могу ли я переслать (тип) универсальную перегруженную функцию без создания синтаксических монстров? - PullRequest
4 голосов
/ 17 октября 2019

Это всего лишь образовательный вопрос, которого в противном случае можно было бы полностью избежать, используя такие инструменты, как range-v3.

В любом случае, рассмотрим этот пример, в котором переменное число контейнеров передается функции, котораявозвращает кортеж итераторов:

#include <tuple>
#include <iostream>
#include <vector>

template <typename ...T> 
auto begin(T&... containers) { return std::tuple( begin(containers)... ); }

int main() {
  std::vector<int> a{1,2,3};
  std::vector<long> b{1000,2000,3000};

  auto bg = begin(a,b);
  (void) bg;
  return 0;
}

Довольно много дублирования кода будет получено путем добавления функций для всех остальных создателей итераторов (std :: [end, cbegin, cend, rbegin, rend]),Поэтому я искал способ переслать универсальную функцию создания итератора в мою функцию. Мне удалось придумать это:

template <auto F, typename ...T>
auto make(T&... containers) { return std::tuple( F(containers)... ); }
// called as this:
auto bg = make<[](auto& c){return std::begin(c);}> (a,b);

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

auto bg = make<std::begin>(a, b); // or
auto bg = make(std::begin, a, b);

но я не смог заставить этих красавиц работать ...

Ответы [ 3 ]

3 голосов
/ 17 октября 2019

но я не смог заставить этих красавиц работать ...

В настоящее время невозможно заставить этих красавиц работать. Корень всего этого в том, что std::begin и др. Не являются функциями. Это функциональные шаблоны . То есть они представляют не одну функцию, а целую семью. То же самое верно и в случае регулярных перегрузок. В тот момент, когда имя функции означает больше, чем одну функцию, оно не может быть передано. Перегрузочный набор - это не осязаемая вещь, которую мы можем передать как ссылку на тип или функцию. На самом деле это не вызываемый объект.

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

Было выдвинуто предложение ( p1170 ) автоматически поднять набор перегрузки в вызываемый объект, но пока он, похоже, не набирает обороты. Таким образом, у C ++ 20 нет средств.

Что касается стандартного примера, мы можем сократить его, если мы хотим использовать макросы. Простой макрос, который корректно поднимает набор перегрузки, может выглядеть примерно так:

#define LIFT(...) [](auto&& ...args)                                           \
        noexcpet(noexcpet(__VA_ARGS__(std::forward<decltype(args)>(args)...))) \
     -> decltype(auto) {                                                       \
    return __VA_ARGS__(std::forward<decltype(args)>(args)...);                 \
}

Конечно, это само по себе много шаблонов, но он обрабатывает спецификацию исключений, а также вывод типа возврата для функций, которыене возвращать по значению. Это также идеально подходит для форвардов. Так что, хотя это действительно довольно уродливо, оно позволяет нам написать:

auto bg = make(LIFT(std::begin), a, b);
1 голос
/ 17 октября 2019

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

make(std::begin, a, b);

т.е.

template <typename F, typename ...T> 
decltype(auto) make(F func, T&... containers) { return std::tuple( func(containers)... ); }

namespace tl {

auto begin = [](auto& c) {
    return std::begin(c);
};

}

int main() {
  std::vector<int> a{1,2,3};
  std::vector<long> b{1000,2000,3000};

  auto bg = make(tl::begin, a, b);
  (void) bg;

  cout << *++std::get<0>(bg) << ' ' << *std::get<1>(bg);

  return 0;
}
0 голосов
/ 18 октября 2019

В зависимости от вашего определения монстра, это легко достижимо:

int main() 
{
    auto v1 = std::vector{1,2,3};
    auto v2 = std::vector{4,5,6};

    auto begins = std::tie(v1, v2) >> notstd::begin;
    auto ends = std::tie(v1, v2) >> notstd::end;

  return 0;
}

Вот пример:

#include <vector>
#include <tuple>
#include <utility>

namespace notstd
{
    namespace detail
    {
        template<class Tuple, class Function, std::size_t...Is>
        auto transform_elements(Tuple&& tup, Function f, std::index_sequence<Is...>)
        {
            return std::make_tuple(f(std::get<Is>(std::forward<Tuple>(tup)))...);
        }
    }

    template<class Tuple, class Transform>
    auto operator >> (Tuple&& tup, Transform t)
    {
        using tuple_type = std::decay_t<Tuple>;

        constexpr auto size = std::tuple_size<tuple_type>::value;

        return detail::transform_elements(std::forward<Tuple>(tup), 
                                          t, 
                                          std::make_index_sequence<size>());
    }

    constexpr auto begin = [](auto&& x)
    {
        return std::begin(x);
    };

    constexpr auto end = [](auto&& x)
    {
        return std::end(x);
    };
}

Это ни в коем случае не рекомендация стиля.

...