1

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?

manlio
  • 18,345
  • 14
  • 76
  • 126
Richard Vock
  • 1,286
  • 10
  • 23
  • `if anyone can reproduce this bug.` Have you checked the [bug tracker](http://gcc.gnu.org/bugzilla/)? –  Feb 02 '14 at 16:20
  • I unfortunately wasn't able to find a bug related to this matter, no. – Richard Vock Feb 02 '14 at 16:28
  • 1
    I can reproduce this ICE on `gcc version 4.9.0 20131223 (experimental) (GCC) ` –  Feb 02 '14 at 16:50
  • 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 Feb 02 '14 at 17:02
  • 2
    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 Feb 03 '14 at 22:04
  • @KeithThompson: Added first lines of pastebin error message dump to make the question self-contained. – Richard Vock Feb 04 '14 at 21:43

1 Answers1

4

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Hm... you might have a valid point, here. Regarding the capture by forwarding you have in your example you might wanna have a look at my question "capture by universal reference" and especially the answers: https://stackoverflow.com/questions/21238463/capture-by-universal-reference (the second one explains why `[x = std::forward(x)]` maybe does not do what you expect it to) – Richard Vock Feb 04 '14 at 21:39
  • @RichardVock Hmm, it makes a copy. That will at least be safe, if less than efficient in all cases. Not sure how to conditionally capture by reference or by copy depending on if the value passed in is an rvalue or lvalue: mayhap a conditional reference-wrapper-or-T instance captured by-value? Note I did say I lack a C++1y compiler, so the above code is a touch blind. :) – Yakk - Adam Nevraumont Feb 04 '14 at 21:49
  • That's perfectly fine. I'll look deeper into the matter once the ICE has been figured out. Adam is currently looking into the matter (which seems more complicated than I thought): http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60033 Oh and btw: If you use coliru and then use clang++ -std=c++1y you get a c++1y compiler that is already standard compliant... http://coliru.stacked-crooked.com/ – Richard Vock Feb 05 '14 at 15:07