Search code examples
c++creferencelanguage-lawyer

Is passing a reference to a primitive from C++ to C context undefined behaviour?


I'm wondering about the behaviour of passing references of an int or float from C++ to C. So the called function expects an int, but I call with an int& Is this defined?

The compiler will obviously recognize the type mismatch, but even if I enable all warnings. I don't get any. Is it just treated like an int& when I want to evaluate an expression?

I couldn't find any reference for this. Hence, the question.

I tried to execute the following code, to see if I get any errors or warnings, but compilers seem to just eat it.

foo.h:

#ifdef __cplusplus
extern "C" {
#endif

void foo(int x);

#ifdef __cplusplus
}
#endif

foo.c:

#include foo.h
#include <stdio.h>

void foo(int x) {
    printf("%d\n", x);
}

main.cpp

#include "foo.h"

int main()
{
    int i = 42;
    int& r = i;
    foo(r);
    
}

I also get the expected output of 42, but is this because compilers are nice, or because this is actually legal?


Solution

  • It seems there are always some confusion about the use of references and expressions. This is an expansion on the comment by @user17732522 and something that I really want to reiterate.

    foo(r);
    

    This is a function-call expression (a special case of postfix expression), which contains the subexpressions foo and r. The r here is an expression of int type and its value category is lvalue. Please remember that in C++ the type of an expression can never be a reference. This is something I feel I cannot emphasize enough.

    What if you call it using i instead?

    foo(i);
    

    Here, the expression i is also of type int and its value category is also lvalue, and we know that i and r refer to the same object, so foo(i) and foo(r) will be exactly the same in any way you can imagine. We can make this conclusion without any knowledge about foo. It can be a function that takes the parameter by value. It can be function that takes the parameter by (lvalue or rvalue) reference. It can be a functor with a call operator. It can be an overload set. It can be extern "C" or not. None of these matters. I think the answer by @Salgar's is a little bit flawed because that foo takes parameter by value is not relevant, at all. No matter what foo here is, the expression foo(i) and foo(r) will always be the same because the expression r and i are exactly equivalent, and it is impossible for one of them to be well-defined while the other not.

    Here is something I want to reiterate, the reference type is only relevant when you initialize a reference of type T& (or T&& (meaning rvalue-reference, not forwarding-reference) for that matter) to bind to an object of type T. One the initialization is done, it is exactly equivalent to the referred-to object in any observable way (except for the special language rule for decltype for id-expressions) when used as an expression. There are more to it if you bind U& or U&& to a T where T and U are not the same, but that's beyond the scope of this question. The bottom line is, you should focus on the type and value category of an expression, instead of how it is originally declared in the case it happens to be an id-expression.

    So the conclusion to your original question is: the behavior is well defined.