Какой компилятор, если есть, имеет ошибку в расширении пакета параметров?

При экспериментировании с удобными способами доступа к кортежам в качестве контейнеров я написал тестовую программу.

на clang (3.9.1 и яблочный clang) он компилируется, как ожидалось, создавая ожидаемый результат:

1.1
foo
2

на gcc (5.4, 6.3), он не скомпилируется:

<source>: In lambda function:
<source>:14:61: error: parameter packs not expanded with '...':
             +[](F& f, Tuple& tuple) { f(std::get<Is>(tuple)); }...
                                                             ^
<source>:14:61: note:         'Is'
<source>: In function 'decltype(auto) notstd::make_callers_impl(std::index_sequence<Is ...>)':
<source>:14:64: error: expansion pattern '+<lambda>' contains no argument packs
             +[](F& f, Tuple& tuple) { f(std::get<Is>(tuple)); }...
                                                                ^~~
Compiler exited with result code 1

Вопрос: кто прав? Может ли быть исправлено?

Программа:

#include <iostream>
#include <array>
#include <tuple>

namespace notstd {

    template<class F, class Tuple, std::size_t...Is>
    auto make_callers_impl(std::index_sequence<Is...>) -> decltype(auto)
    {
        static std::array<void (*) (F&, Tuple&), sizeof...(Is)> x =
        {
            +[](F& f, Tuple& tuple) { f(std::get<Is>(tuple)); }...
        };
        return x;
    };

    template<class F, class Tuple>
    auto make_callers() -> decltype(auto)
    {
        return make_callers_impl<F, Tuple>(std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
    };

    template<class Tuple, std::size_t N = std::tuple_size<std::decay_t<Tuple>>::value >
    struct tuple_iterator {
        static constexpr auto size = N;

        constexpr tuple_iterator(Tuple& tuple, std::size_t i = 0) : tuple(tuple), i(i) {}

        template<class F>
        void with(F&& f) const {
            static const auto& callers = make_callers<F, Tuple>();
            callers[i](f, tuple);
        }

        constexpr bool operator!=(tuple_iterator const& r) const {
            return i != r.i;
        }

        constexpr auto operator++() -> tuple_iterator& {
            ++i;
            return *this;
        }


        Tuple& tuple;
        std::size_t i;
    };

    template<class Tuple>
    auto begin(Tuple&& tuple)
    {
        return tuple_iterator<Tuple>(std::forward<Tuple>(tuple));
    }

    template<class Tuple>
    auto end(Tuple&& tuple)
    {
        using tuple_type = std::decay_t<Tuple>;
        static constexpr auto size = std::tuple_size<tuple_type>::value;
        return tuple_iterator<Tuple>(std::forward<Tuple>(tuple), size);
    }

}

template<class T> void emit(const T&);

int main() {
    auto a = std::make_tuple(1.1, "foo", 2);
    auto i = notstd::begin(a);
    while(i != notstd::end(a))
    {
        i.with([](auto&& val) { std::cout << val << std::endl; });
        ++i;
    }
}

Ответ 1

Это gcc ошибка 47226. gcc просто не позволяет создавать пакетные расширения лямбда. Ошибка все еще присутствует в 7.0.


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

template <size_t I, class F, class Tuple>
void lambda(F& f, Tuple& tuple) {
    f(std::get<I>(tuple));
}

static std::array<void (*) (F&, Tuple&), sizeof...(Is)> x = 
{
    lambda<Is,F,Tuple>...
};   

Ответ 2

clang прав.

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

Ниже приведено встроенное обходное решение:

template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>
template<std::size_t I>
constexpr index_t<I> index{};

то внутри функции:

  auto lamb = [](auto I){
    using I_t=decltype(I);
    return [](F& f, Tuple& tuple) { f(std::get<I_t::value>(tuple)); };
  };
  static std::array<void (*) (F&, Tuple&), sizeof...(Is)> x =
  {
    +(lamb(index_k<Is>))...
  };

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

Другой шаблон:

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
  return [](auto&&f)->decltype(auto){
    return decltype(f)(f)( index_k<Is>... );
  };
}
template<std::size_t N>
auto index_upto(index_t<N>={}){
  return index_over(std::make_index_sequence<N>{});
}
template<class F>
auto array_maker(F f){
  return [f=std::move(f)](auto...Is)->std::array<decltype(f(index_k<0>),sizeof...(Is)>{
    return {{f(Is...}};
  };
}

это позволяет полностью уклониться от вашей проблемы и убивает им:

template<class F, class Tuple>
auto make_callers() -> decltype(auto)
{
  auto size=index_k<std::tuple_size<std::decay_t<Tuple>>{}>;
  auto indexer=index_upto(size);
  auto make_array=array_maker([](auto I){
    return +[](F& f, Tuple& tuple) { f(std::get<decltype(I)::value>(tuple)); };
  });
  return indexer(make_array);
}

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