Error in C++ code linkage: warning C4190: type has C-linkage specified, but returns UDT which is incompatible with C

12,542

Solution 1

The reason is when you compile that code with a C++ compiler, the preprocessed code looks like this:

typedef struct vec3 {
    float   x;
    float   y;
    float   z;
    vec3(float x, float y, float z) : x(x), y(y), z(z) {}
} vec3;

extern "C" {
vec3    vec3_add(vec3 a, vec3 b);
}

So what the compiler sees is the function 'vec3_add' declared as having C linkage, but using the type 'vec3' which has as a constructor which a C compiler won't understand. The C++ compiler doesn't know that a C compiler won't see the constructor so emits the warning. Remember, preprocessing happens before compilation, so the #ifdef __cplusplus lines are not visible to the compiler at the time when the warning is reported.

A common pattern for this sort of issue is:

extern "C" {

typedef struct vec3 {
    float   x;
    float   y;
    float   z;
} vec3;

vec3 vec3_add(vec3 a, vec3 b);

}

#ifdef __cplusplus

struct CVec3 :vec3 {
    CVec3(float X, float Y, float Z) { x = X; y = Y; z = Z; }
};

#endif

In this way, C++ code can use the 'CVec3' type instead of the 'vec3' type in calls to 'vec3_add'. C code will only see the 'vec3' POD type.

Solution 2

The error message is pretty clear; when you are using extern "C" you can only use code that is compatible with the C ABI. Non-POD structs (e.g. struct with a user-defined constructor) is not compatible with the C ABI.

You should remove this from the struct definition:

#ifdef __cplusplus
    vec3(float x, float y, float z) : x(x), y(y), z(z) {}
#endif

It probably causes undefined behaviour having it there anyway, since adding this in may cause the struct layout to change. If you want to be able to tidily construct a vec3 in C++ code then write a function to do so, e.g.

vec3 make_vec3(float x, float y, float z) { vec3 v; v.x=x; v.y=y; v.z.z; return v; }

Also you should wrap the entire header in extern "C" (except for standard headers included from it), not just bits and pieces of it.

Share:
12,542
kin
Author by

kin

Updated on June 15, 2022

Comments

  • kin
    kin almost 2 years

    I am having a hard time understanding why the following code (UDT with standard layout) gives a C-linkage warning in visual C++ 2012:

    warning C4190: 'vec3_add' has C-linkage specified, but returns UDT 'vec3' which is incompatible with C
    
    
    
    typedef struct vec3 {
        float   x;
        float   y;
        float   z;
    #ifdef __cplusplus
        vec3(float x, float y, float z) : x(x), y(y), z(z) {}
    #endif
    } vec3;
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    vec3    vec3_add(vec3 a, vec3 b);
    #ifdef __cplusplus
    }
    

    The definition of the function is in a C++ file:

    vec3
    vec3_add(vec3 a, vec3 b) {
        static_assert(std::is_standard_layout<vec3>::value == true, "incompatible vec3 type");
        return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
    }
    
    • Cheers and hth. - Alf
      Cheers and hth. - Alf about 10 years
      the compiler is apparently reacting to the constructor. maybe it (a bit paradoxically) could help to add a default constructor. i wouldn't place too much weight on what is_standard_layout reports. this stuff was often dummies in the original Boost implementation, and may not be correctly implemented or "really" implemented.
    • Cheers and hth. - Alf
      Cheers and hth. - Alf about 10 years
      Note that by upping the compiler version one notch, you get support for curly braces initialization, where the constructor provides very little in the way of convenience. For your current compiler you can replace the constructor with an inline object factory function.
    • kin
      kin about 10 years
      From the definition of the class it is indeed a standard layout class. The question is should it be a POD type for it work ?! - POD are trivial and standard layout.
  • kin
    kin about 10 years
    Nop, this will generate the same warning message twice
  • M.M
    M.M almost 10 years
    As long as the function is declared with extern "C", the definition "inherits" that.
  • singingsingh
    singingsingh almost 7 years
    what does the single colon do in the line struct CVec3 :vec3
  • alvion
    alvion over 3 years
    It's a derived type. CVec3 is a subclass of vec3.
  • JohnAl
    JohnAl almost 2 years
    Is the solution from the OP objectively wrong and undefined behavior, or is it purely a matter of this common pattern you suggested (a C++ struct that inherits from the C struct and adds a constructor) being prettier and not causing a warning? I think as of C++20, it can be said that OPs solution is valid as long as std::is_standard_layout for the struct is true, right?
  • JohnAl
    JohnAl almost 2 years
    Why would "adding this in may cause the struct layout to change" and "probably cause undefined behaviour"? Could you give some example for why it could ever happen with a simple constructor? As far as I know, I think it's impossible for a constructor like this to change the layout. As of C++20, as long as std::is_standard_layout for the struct is true (which it is in this case) it should be defined behavior.
  • M.M
    M.M almost 2 years
    @JohnAl at time of writing this answer I thought that would render it non-standard layout. It no longer does as of c++20 as you say (and not sure if it ever did)