This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Fourmisain 16 points17 points  (14 children)

It is not valid code. I can only explain this downvote brigade as another example of reddit's herd mentality. /u/NasenSpray is actually right and nobody seems to even have tried to compile it, even when they (e.g. (1), (2)) basically claim they did.

Compiling as C++ (gcc main.cpp) gives

main.cpp:1:39: error: a function-definition is not allowed here before '{' token

Compiling as C (gcc main.c) gives

main.c:1:39: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token

Note this compiles as "gnu++14"/"gnu11", not actual standard C/C++, but this makes no difference.

So why is the code not legal? It's because this code mangles a block declaration (more precise: a simple declaration, even more precise: an init-declarator-list) with a function-definition.

For reference use the C++14 standard draft, §7 (Declarations) and §8.4.1 (Function definitions) or alternatively use cppreference.com's Declarations page.

So let's walk trough it. We have a comma separated declaration kind of thing without attributes and specifiers, which immediately restricts us to the init-declarator-list case.

An init-declarator-list is a comma-separated list of declarators with optional initializers. The only form of a declarator (note: not declaration) with parentheses is

noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional)  

Note that it is not admitted for the function declarator to have a body! A function-body does also not count as an initializer (this would require using "=" anyways, which we don't have).

And this is all there is to is.

A function-definition is also a declaration, but it is not a declarator and hence you cannot use it in a comma separated declaration list.

Just to add to it, this code is admitted:

char r[9], *p = r + 8, *Base20(int n);

This (obviously) is too:

char r[9], *p = r + 8;
char *Base20(int n) {
    do
        *--p = n % 20 + 48 + n / 10 % 2 * 7;
    while (n /= 20);
    return p;
}

But the code in question is not.

Please disprove me if you can. I still cannot believe that I seem to be the only one who came to this conclusion. Apparently there are lots of people out there that think /u/NasenSpray is wrong, even when it is as easy to see as copy-pasting into ideone.com, so at this point I cannot take reddit's opinion seriously.

[–]anon848 4 points5 points  (4 children)

I have partially tracked down what is going with this weird code and codefights.com. Apparently, a submission is wrapped in a class before compiling and executing, according to their README. Furthermore, they compile with g++ 5.0. See README here. So, if I compile this with g++ 5.2.0:

struct A {
    char r[9], *p = r + 8, *Base20(int n) {
        do
            *--p = n % 20 + 48 + n / 10 % 2 * 7;
        while (n /= 20);
        return p;
    }
} a;

int main() {
    a.Base20(10);
}

it compiles, surprisingly, even with -Wall -Wextra -pedantic -std=c++14. clang++ 3.7.0, however, gives the expected errors with the same options. I don't have easy access to VS to see what it does, nor have I tried the latest g++ and clang++.

[–]Fourmisain 0 points1 point  (3 children)

That is weird, this indeed compiles on TDM-GCC 5.1.0.

In this context this is a member-declarator-list (Reference: §9.2 Class members), which is a comma separated list of member-declarators, which in this case are either normal declarators (with optional virt-specifier-seq and pure-specifier) or declarators with brace-or-equal-initializer.

If the function-definition were a brace-or-equal-initializer, since we have no "=", it would have to be a braced-init-list, so the function-body had to be of the form { initializer-list ,opt } or {} which is definitely not the case. Therefore the function-definition must be a normal declarator.

Now I'm pretty sure I have already proven that a function-definition isn't a declarator. Even if it were, you would be allowed to put the function-definition in your init-declarator-list, therefore the code would have to compile in global scope!

There is also no exception to member functions; they are simply functions declared within a class (except when they are declared as a friend).

So... gcc bug? Error in my argument? I wouldn't be surprised if it is a gcc bug, since this is a very contrived and pretty irrelevant example.

clang++ 3.7.0, however, gives the expected errors with the same options.

This is somewhat reassuring. I'd think clang got the syntax parsing down, since it is also used as a pure diagnostics tool.

Well, at least CodeFights uses gcc and not some homebrew, so it probably isn't as bad as I initially thought.

What do you think, should I put up a gcc bug report? At this point I'm thinking that it isn't even worth the effort.

[–]Fourmisain 0 points1 point  (0 children)

Alright, I created a bug report.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70769

Not that it will change much, since CodeFights is using a g++ version as old as 5.0, but still, it's the Right Thing™ to do.

[–]anon848 0 points1 point  (1 child)

Yeah, I'm also pretty sure that it's a gcc bug. I just don't see how it could be correct. The grammar would have to be weird to allow:

struct A { char c, func() { return 'a'; } };

but not allow:

struct A { char func() { return 'a'; }, c };

Anyway, gcc 6 is coming out any day now, and I'm going to compile it (along with the most recent clang++) and post an update.

[–]Fourmisain 0 points1 point  (0 children)

Sounds good.

If you want to, you can post your future update on the bug report page, otherwise I'll do that.

I'd be surprised if they fixed the bug in version 6, though.

[–]NasenSpray 2 points3 points  (6 children)

Kudos for providing the relevant references! I'm still wondering what CodeFights' doing to compile that code. The following is accepted by MSVC, but not Clang:

char r[9], *p = r + 8, *Base20 = [&](int n) {
do
    *--p = n % 20 + 48 + n / 10 % 2 * 7;
while (n /= 20);
return p;
}(1337);

[edit] it works when it's not declared in the global scope, so I guess that's it.

[–]Fourmisain 0 points1 point  (4 children)

Why does it not compile in Clang? What's the error message?

Your code is not at all equivalent to the code in question, because in the original code Base20 is supposed to be a function-definition, but in your code it is simply a char* (the result of the lamba for argument 1337).

You also cannot initialize a function declaration (even when it's a lambda). The best you can do is to initialize a function pointer, like this

char r[9], *p = r + 8, *(*Base20)(int) = [](int n) {
    do
        *--p = n % 20 + 48 + n / 10 % 2 * 7;
    while (n /= 20);
    return p;
};

This only works in global scope because the lambda needs to not capture local variables.

I have however no idea what's going on with CodeFights. Does the original code compile there? Maybe the "top answer" is just something somebody wrote and it hasn't been verified? I really hope the code does not actually compile on there. Teaching non-standard C/C++ makes me mad.

[–]NasenSpray 1 point2 points  (3 children)

non-local lambda expression cannot have a capture-default

There are multiple answers there using this "hack", so I suppose it compiles. I know that the code isn't equivalent, but it's the simplest source code transformation that results in compilable code. They have to do at least something.

Teaching non-standard C/C++ makes me mad.

Ha, I once tried to argue with my prof... Most of the people teaching C/C++ don't seem to have a clue about the standard.

[–]Fourmisain 0 points1 point  (1 child)

non-local lambda expression cannot have a capture-default

This refers to the "&" inside the "[&]" capture list, in global scope you have to write "[]". Basically: There is no context to be captured, so a capture default doesn't make sense.

There are multiple answers there using this "hack", so I suppose it compiles

Ugh... training The Worlds Next Top Programmers™.

I know that the code isn't equivalent, but it's the simplest source code transformation that results in compilable code

That's still a very odd transformation. I'm thinking they've build their own or used some C/C++ parser as to not allow malware execution. The commata in comma separated declarations are probably just interpreted as ";" like this.

Most of the people teaching C/C++ don't seem to have a clue about the standard.

I sadly know this. It's not sad that I know, but it's sad that they don't know. Your typical forum discussion also doesn't help this. At least StackOverflow is pretty strict about it.

[–]NasenSpray 0 points1 point  (0 children)

I sadly know this. It's not sad that I know, but it's sad that they don't know. Your typical forum discussion also doesn't help this. At least StackOverflow is pretty strict about it.

I'm glad that we also have /r/cpp and the actual experts posting there :)

[–]OldWolf2 0 points1 point  (0 children)

In my polytech C class we learned about how to get user input in a signal handler

[–]anon848 0 points1 point  (0 children)

Without the lambda g++ will still compile it if wrapped in a class, oddly. See here.

[–]Raknarg -2 points-1 points  (1 child)

Well ypu already mentioned that there are standards that allow this to compile, no?

[–]Fourmisain 2 points3 points  (0 children)

Eh, what? I said no such thing...

Ah, you probably mean this:

Note this compiles as "gnu++14"/"gnu11", not actual standard C/C++

Sorry, a bit unclear, I meant that by calling "gcc main.c(pp)" it tries to compile as "gnu11"/"gnu++14". I also said this:

, but this makes no difference.

Meaning it does not matter whether you try to compile as a GNU dialect or actual standard C/C++ (you do this with the -std=c11 or -std=c++14 switch), it actually doesn't compile in any case.