tags:

views:

360

answers:

3

The following source file will not compile with the MSVC compiler (v15.00.30729.01):

/* stest.c */
#ifdef __cplusplus
extern "C" {
#endif

struct Test;

/* NB: This may be extern when imported by another module. */
struct Test make_Test(int x);

struct Test { int x; };

struct Test make_Test(int x)
{
    struct Test r;
    r.x = x;
    return r;
}

#ifdef __cplusplus
}
#endif

Compiling with cl /c /Tpstest.c produces the following error:

stest.c(8) : error C2526: 'make_Test' : C linkage function cannot return C++ class 'Test'
        stest.c(6) : see declaration of 'Test'

Compiling without /Tp (which tells cl to treat the file as C++) works fine. The file also compiles fine in DigitalMars C and GCC (from mingw) in both C and C++ modes. I also used -ansi -pedantic -Wall with GCC and it had no complaints.

For reasons I will go into below, we need to compile this file as C++ for MSVC (not for the others), but with functions being compiled as C. In essence, we want a normal C compiler... except for about six lines. Is there a switch or attribute or something I can add that will allow this to work?


The code in question (though not the above; that's just a reduced example) is being produced by a code generator.

As part of this, we need to be able to generate floating point nans and infinities as constants (long story), meaning we have to compile with MSVC in C++ mode in order to actually do this. We only found one solution that works, and it only works in C++ mode.

We're wrapping the code in extern "C" {...} because we want to control the mangling and calling convention so that we can interface with existing C code. ... also because I trust C++ compilers about as far as I could throw a smallish department store. I also tried wrapping just the reinterpret_cast line in extern "C++" {...}, but of course that doesn't work. Pity.

There is a potential solution I found which requires reordering the declarations such that the full struct definition comes before the function foward decl., but this is very inconvenient due to the way the codegen is performed, so I'd really like to avoid having to go down that road if I can.

A: 

Why do you have the extern in

extern struct Test make_Test(int x)
{
    struct Test r;
    r.x = x;
    return r;
}

It's not extern, you are defining it right there.

MK
Yes, I probably should have removed that. That's just the way the code is generated at the moment. The problem definitely still occurs with extern removed, so I'll cut that out.
DK
+1  A: 

It's a bit of a screwy error message, but the caller needs to know the size of the structure to be able to make the call. It needs to reserve space on the stack for the return value. Or expect the return value in registers if the structure is small enough.

You have to declare the structure in the header. When you do, the error disappears:

#ifdef __cplusplus
extern "C" {
#endif

struct Test { int x; };
struct Test make_Test(int x);

struct Test make_Test(int x)
{
    struct Test r;
    r.x = x;
    return r;
}

#ifdef __cplusplus
}
#endif
Hans Passant
What I've found perplexing is that the code works with MSVC *when compiled as C*. Is this something which DMC, GCC and MSVC in C mode are simply more lax about, or is MSVC in C++ mode simply more demanding?
DK
+2  A: 

This is an interesting question. As you say, compiling the code as C code rightly produces no error. And only MSVC seems to have trouble with it when compiled as C++ code.

Since other C++ compilers don't have a problem with the code, this might be a bug in MSVC, but I can see how MSVC might have a rationale for this error. When the C++ compiler hits the line:

struct Test;

That's an incomplete declaration of struct Test - the compiler doesn't know if the complete definition of struct Test will contain C++ specific items (virtual functions, inheritance, etc). Note that types in an extern "C" block can still use all C++ facilities; the extern "C" language linkage specification applies only to "function types of all function declarators, function names, and variable names introduced by the declaration(s)" (7.5/4 "Linkage specifications").

So I could see how when MSVC's C++ compiler comes across an extern "C" function that's returning an incomplete type, it might decide that it needs to return an error at that point in case the type turns out to not be a plain C-style POD type.

The C++ standard does say (7.5/9 "Linkage specifications"):

Linkage from C++ to objects defined in other languages and to objects defined in C++ from other languages is implementation-defined and language-dependent. Only where the object layout strategies of two language implementations are similar enough can such linkage be achieved.

So MSVC might have some leeway (standards-wise) if it has a reason to not permit extern "C" functions from returning non-POD objects, though I'm not sure why MSVC would have a problem when other Windows compilers don't. If anyone knows details (or if they know I'm just plain off-base here), I'd appreciate a note.

Not that any of this this helps you - it's just my guess at a rationale.

Without knowing more about your codegen process and how you might be able to influence it, I'm not sure what decent options you might have - maybe a post-processing of the generated files to split out the stuff that needs to be compiled as C (or rearranges the declarations). But I can imagine that that might be a nightmare to get working and especially to maintain.

Michael Burr
Thanks for the detailed answer. I decided to accept yours on the basis of actually having looked up the C++ spec to explain what's going on.Not that Hans' answer wasn't useful. :)
DK