Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a lambda have "C" linkage?

The title more or less says it all. I've got the following bit of code:

#include <vector>
#include <string>
#include <iterator>
#include <algorithm>

struct xloper12;

class Something
{
public:
    std::string asString() const;
};
extern std::vector<Something> ourSomethings;

class ExcelOutputLoader
{
public:
    void load( std::vector<std::string> const& value );
    xloper12* asXloper() const;
};

extern xloper12* ProcessException( std::string const& functionName );

extern "C" __declspec(dllexport) xloper12*
getSomethingList()
{
    try {
        std::vector<std::string> results;
        results.reserve( ourSomethings.size() );
        std::transform(
            ourSomethings.begin(),
            ourSomethings.end(),
            std::back_inserter(results),
            []( Something const& o ) { return o.asString(); } );
        ExcelOutputLoader out;
        out.load( results );
        return out.asXloper();
    } catch (...) {
        return ProcessException( "GetSomthing" );
    }
}

I've replaced most of the non-standard headers with dummy declarations; the issue is in the last function (which is designed to be called from Excel). Basically, when compiled with Visual Studios 2012, I get the following warning:

falseWarning.cc(34) : warning C4190: '<Unknown>' has C-linkage specified, but re
turns UDT 'std::basic_string<_Elem,_Traits,_Alloc>' which is incompatible with C

        with
        [
            _Elem=char,
            _Traits=std::char_traits<char>,
            _Alloc=std::allocator<char>
        ]

(repeated four times, for good measure). But as I understand it, lambda defines a class with an operator() member, and not a function. And (§7.5/4) "A C language linkage is ignored in determining the language linkage of the names of class members and the function type of class member functions." Which would mean that the extern "C" should be ignored on the lambda.

It's not a big thing: it's only a warning, and it's easy to work around (have the extern "C" function call a C++ function which does the actual work). But I would still like to know: is there something fundamental that I've not understood about lambda, or is it the people developing Visual C++ who don't understand it. (In the latter case, I'm worried. Since portability isn't an issue, we've started using lambda intensively. But if the author's of the compiler don't understand it, then I'm worried.)

EDIT:

Some more tests. If I write something like:

extern "C" __declspec(dllexport) void
funct1()
{
    extern std::string another();
}

I also get the warning. This time, I would say it was correct. another is a function in namespace scope, and it is declared inside an extern "C" block, so it should have "C" linkage. (Interestingly enough, I also get a warning to the effect that I might have been bitten by the most vexing parse problem. The extern should have been enough for the compiler to realize that I wasn't trying to define a local variable.)

On the other hand, if I write something like:

extern "C" __declspec(dllexport) void
funct2()
{
    class Whatever
    {
    public:
        std::string asString() { return std::string(); }
    };
    std::string x = Whatever().asString();
}

There is no warning. In this case, the compiler does correctly ignore the specified "C" linkage on a member function.

Which makes me wonder a little bit. Is the compiler treating the lambda as a class with an operator() function (as it should), or is it treating it as a function? It looks like that latter, and that makes me worry if there aren't other subtle problems involved, probably only visible when there is capture (and probably only in very special cases then).

like image 620
James Kanze Avatar asked Jan 08 '14 16:01

James Kanze


2 Answers

The 4 errors is what clued me in.

Stateless lambdas have an implicit conversion to functions. In MSVC, there are something like 4 calling conventions supported.

So your lambda creates 4 function signatures within the extern "C" block, one per calling convention. These function signatures pick up the extern "C" and become illegal, as they return std::string.

A possible fix might be to split the body from the interface. Either one step (extern "C" the prototype, then implement), or have your extern "C" function call a non-extern inline function that has the lambda.

Another approach would be to create a dummy variable and capture it.

It isn't the operator() that is generating the error, it is the signature-matching pure function pointers implied by a pure stateless lambda.

like image 20
Yakk - Adam Nevraumont Avatar answered Oct 07 '22 15:10

Yakk - Adam Nevraumont


This appears to be underspecified by the standard.

5.1.2:

3 - [...] The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [...]
5 - The closure type for a lambda-expression has a public inline function call operator [...]
6 - The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.

7.5:

4 - [...] In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification. [...] A C language linkage is ignored in determining the language linkage of the names of class members and the function type of class member functions. [...]

So neither the function call operator or the conversion function to function pointer have C language linkage as they are class member functions; but since 5.1.2p6 does not specify where the function returned by the conversion function is declared, its type may have C language linkage.

On one hand, if we consider an example in 7.5p4:

extern "C" {
  class X {
  // ...
  void mf2(void(*)()); // the name of the function mf2 has C++ language
                       // linkage; the parameter has type pointer to
                       // C function
  };
}

This suggests that a conversion to function pointer would have return type pointer to C function, as long as the C function type is declared inline of the conversion declaration or otherwise within the extern "C" block:

extern "C" {
  class Y {
    (*operator void())(); // return type pointer to C function
  };
}

On the other hand, the function is required to have the same effect as the function call operator, which is not possible if C language linkage prevents it; we could conclude that the function must be declared outside the extern "C" block and similarly the return type of the conversion function. But this may impose extra workload on the compiler writers.

like image 153
ecatmur Avatar answered Oct 07 '22 15:10

ecatmur