1
votes

I have a small snippet of code that compiles fine in clang repo head (3.5) but not in gcc 4.9 repo head. Although this looks like a gcc bug, before spamming the bugzilla I'd wanted to ask you if

  1. this is valid c++1y code (in the current draft state) - just because clang compiles it that's no reason for it to be correct code, and
  2. if anyone can reproduce this bug.

The code snippet compiling and running using clang is here:

http://coliru.stacked-crooked.com/a/acc691b9a407d6f2

However using

g++-4.9 -o main main.cpp -std=c++1y

gives me the aforementioned internal compiler error: http://pastebin.com/3fqV7xzC

Without the long dump it reads:

g++-4.9 -o main main.cpp -std=c++1y main.cpp: In instantiation of ‘composer::operator()(Func&&, Funcs&& ...):: [with auto:2 = float; Func = main(int, const char*)::; Funcs = {main(int, const char*)::}]’: main.cpp:33:88: required from here

main.cpp:19:41: internal compiler error: in retrieve_specialization, at cp/pt.c:1042
    return f(c(std::forward<Funcs>(fs)...)(v));
                                         ^

For completeness' sake here is the snippet (the complete main.cpp)

#include <iostream>
#include <utility>

template <typename... Funcs>
struct composer;

template <>
struct composer<> {
    auto operator()() {
        return [&] (auto v) { return v; };
    }
};

template <typename Func, typename... Funcs>
struct composer<Func, Funcs...> {
    auto operator()(Func&& f, Funcs&&... fs) {
        composer<Funcs...> c;
        return [&] (auto v) {
            return f(c(std::forward<Funcs>(fs)...)(v));
        };
    }
};

template <typename... Funcs>
auto compose(Funcs&&... fs) {
    composer<Funcs...> c;
    return c(std::forward<Funcs>(fs)...);
}


int main (int argc, char const* argv[]) {
    float v = 3.5f;
    auto t = compose([] (auto v) { return v >= 3; }, [] (auto v) { return int(v-0.5); })(v);
    std::cout << std::boolalpha << t << "\n";
    auto f = compose([] (auto v) { return v > 3; }, [] (auto v) { return int(v-0.5); })(v);
    std::cout << std::boolalpha << f << "\n";
}

Edit: Bonus! I don't like that code at all - if anyone's got a nicer and probably faster way to do this consider to enlighten me...

Edit 2 Does anyone know how to get coliru (or a similar service) to use g++ 4.9?

1
if anyone can reproduce this bug. Have you checked the bug tracker? - user1508519
I unfortunately wasn't able to find a bug related to this matter, no. - Richard Vock
I can reproduce this ICE on gcc version 4.9.0 20131223 (experimental) (GCC) - user1508519
ok, that's one question answered, thanks for that. Independent of whether this code is correct or not it should not produce ICEs anyway, so I'll post this to the tracker... - Richard Vock
An internal compiler error indicates that this is certainly a gcc bug; that shouldn't happen whether the code itself is valid or invalid. You might want to include at least the first few lines of the error message in your question. - Keith Thompson

1 Answers

4
votes

Your code is not valid C++1y, at least not when executed.

You capture variables by reference, then exit the scope where they are defined, then invoke the lambda which uses said variables.

Now, the state of c is never used, but operator() invocation is still UB.

Similarly, while your references are to data that outlive the current scope, there is no guarantee that the original variables are captured and not the local references. One way to implement local capture is to capture a pointer to the local stack frame, and access variables via compile time static offsets from said stack frame: as you have exited the stack frame, such reading would generate garbage. (This would reduce the size of a [&] lambda down to a single pointer, a pretty good optimization! And on some machines, accessing data via offsets from a pointer is fast.)

In general, do not capture by reference (especially by global reference) if your closure (or copies thereof) will outlive the current scope. A capture-by-global-reference on a return statement should really generate warnings.

Fixing that UB and your code looks valid. In C++1y you can capture-by-move or -forward.

Without a C++1y compiler, here is a stab at it:

#include <iostream>
auto compose() {
  return [](auto&& x) { return std::forward<decltype(x)>(x); };
}

template<typename F>
auto compose( F&& f ) {
  return std::forward<F>(f);
}

template<typename F1, typename F2>
auto compose( F1&& f1, F2&& f2 ) {
  return [lhs = std::forward<F1>(f1), rhs = std::forward<F2>(f2)]( auto&& x ) {
    return lhs( rhs( std::forward<decltype(x)>(x) ) );
  };
}

template<typename F0, typename... Fs>
auto compose( F0&& f0, Fs&&... fs ) {
  static_assert( sizeof...(Fs) > 1, "other overrides should have handled this case!  What went wrong?" );
  return compose(
    std::forward<F0>(f0),
    compose(
      std::forward<Fs>(fs)...
    )
  );
}

int main() {
  auto a = compose( [](bool b){return !b;}, [](double d){ return d<3.0; } );
  std::cout << a(2.0) << "," << a(3.0) << "\n";
  return 0;
}

which also happens to be non-lambda type-less as well.