c++11c++14compiler-buggcc7gcc8

How can I work around this parameter expansion bug in GCC without upgrading to 8.x?


Consider the following code:

#include <iostream>
#include <utility>
#include <array>
#include <functional>
#include <ctime>

template <unsigned N> void foo() { std::cout << N << "(" << ") "; }
template<> void foo<2>() { std::cout << "TWO (" <<  ") "; }

struct Foo {
    template <unsigned N> void operator()(std::integral_constant<unsigned,N>) { foo<N>(); }
};

template <std::size_t Offset, std::size_t... Idx, typename F>
void visit(F f, std::index_sequence<Idx...>, std::size_t n) {
    static std::array<std::function<void()>, sizeof...(Idx)> funcs {{
        [&f](){f(std::integral_constant<unsigned,Idx+Offset>{});}...
    }};

    funcs[n - Offset]();
};

template <std::size_t Start, std::size_t End, typename F>
void visit(F f, std::size_t n) {
    visit<Start>(f, std::make_index_sequence<End-Start>{}, n);
};

int main() {
    auto t = time(nullptr);
    for(int i = 0; i < 10; i++) {
        visit<1, 10>(Foo{}, (t+i) % 10);
    }
}

This is valid C++14 (actually, also valid C++11 if you write your own std::index_sequence). But - it doesn't compile with g++ 6.x and 7.x; only g++ 8.x compiles it properly (see this happening on GodBolt).

For organizational reasons, I can require use g++ version up to 7.2. Is there a way I could alter the code while keeping the semantics, to make g++ 7.x compile it?


Solution

  • GCC's bug is that it can't pack expand lambdas. So don't pack expand lambdas.

    template<class F, std::size_t Idx>
    void caller(F& f) { f(std:::integral_constant<unsigned, Idx>()); }
    
    template <std::size_t Offset, std::size_t... Idx, typename F>
    void visit(F f, std::index_sequence<Idx...>, std::size_t n) {
        using ptr_type = void (*)(F&);
        static constexpr ptr_type funcs[] = {&caller<F, Idx+Offset>...};
        funcs[n-Offset](f);
    }