Может ли лямбда иметь связь "С"?

Название более или менее говорит обо всем. У меня есть следующий бит кода:

#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" );
    }
}

Я заменил большинство нестандартных заголовков манекеном декларации; проблема заключается в последней функции (которая предназначенный для вызова из Excel). В основном, когда скомпилировано с Visual Studios 2012, я получаю следующее предупреждение:

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>
        ]

(повторяется четыре раза, для хорошей меры). Но, насколько я понимаю он, lambda определяет класс с членом operator(), а не функция. И (§7.5/4) "Связывание языка C игнорируется в определение языковой связи имен членов класса и тип функции функций-членов класса. означает, что extern "C" следует игнорировать на лямбда.

Это не большая вещь: это только предупреждение, и это легко работать (функция extern "C" вызывает функцию С++, которая делает фактическую работу). Но я все равно хотел бы знать: есть ли что-то фундаментальное, что я не понял о лямбда, или это люди, разрабатывающие Visual С++, которые этого не понимают. (В последнем случае я беспокоюсь. Поскольку переносимость не является вопрос, мы начали интенсивно использовать лямбда. Но если автор компилятора не понимает этого, тогда я волнуюсь.)

EDIT:

Еще несколько тестов. Если я напишу что-то вроде:

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

Я также получаю предупреждение. На этот раз я бы сказал, что это правильно. another является функцией в области пространства имен и объявляется внутри блока extern "C", поэтому он должен иметь связь "C". (Интересно, что я также получаю предупреждение о том, что Возможно, меня укусила самая неприятная проблема синтаксического анализа. extern должно быть достаточно, чтобы компилятор понял что я не пытался определить локальную переменную.)

С другой стороны, если я пишу что-то вроде:

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

Предупреждений нет. В этом случае компилятор делает правильно игнорируйте указанную ссылку "C" для функции-члена.

Это заставляет меня немного удивляться. Является ли обработка компилятора лямбда как класс с функцией operator() (так как она должен), или он рассматривает его как функцию? Похоже, что последнее, и это заставляет меня беспокоиться, если нет других тонких проблемы, вероятно, видимые только при захвате (и, вероятно, только в особых случаях).

Ответ 1

Это, по-видимому, указано стандартным стандартом.

5.1.2

3 - [...] Тип замыкания объявляется в наименьшей области блока, области видимости класса или области пространства имен, которая содержит соответствующее лямбда-выражение. [...]
5 - Тип замыкания для лямбда-выражения имеет открытый встроенный оператор вызова функций [...]
6 - Тип замыкания для лямбда-выражения без лямбда-захвата имеет публичную не виртуальную неявную функцию преобразования const, чтобы указатель на функцию, имеющую тот же параметр и возвращаемые типы, что и оператор вызова функции закрытия. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции закрытия.

7,5:

4 - [...] В спецификации привязки указанная языковая связь применяется к типам функций всех деклараторов функций, имен функций с внешней привязкой и именам переменных с внешней связью, объявленной в спецификации привязки. [...] Связи языка C игнорируются при определении языковой привязки имен членов класса и типа функции функций члена класса. [...]

Таким образом, ни оператор вызова функции, ни функция преобразования в функцию указателя не имеют C-языка, поскольку они являются функциями класса-члена; но так как 5.1.2p6 не указывает, где объявлена ​​функция, возвращаемая функцией преобразования, ее тип может иметь ссылку на C-язык.

С одной стороны, если мы рассмотрим пример в 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
  };
}

Это говорит о том, что преобразование в указатель функции будет иметь указатель возвращаемого типа для функции C, если тип функции C объявлен в виде строки объявления преобразования или иначе в блоке extern "C":

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

С другой стороны, функция должна иметь тот же эффект, что и оператор вызова функции, что невозможно, если C языковая привязка предотвращает это; мы могли бы заключить, что функция должна быть объявлена ​​вне внешнего блока "C" и аналогично возвращаемого типа функции преобразования. Но это может наложить дополнительную нагрузку на составителей компилятора.

Ответ 2

4 ошибки - это то, о чем я говорил.

Безстоящие lambdas имеют неявное преобразование в функции. В MSVC есть что-то вроде 4 вызывающих соглашений.

Итак, ваша лямбда создает 4 сигнатуры функций в блоке extern "C", по одному на соглашение о вызове. Эти сигнатуры функций поднимают extern "C" и становятся незаконными, поскольку они возвращают std::string.

Возможным решением может быть разделение тела на интерфейс. Или один шаг (extern "C" прототип, затем реализовать), либо ваша функция extern "C" вызывает функцию extern inline, которая имеет lambda.

Другим подходом было бы создание фиктивной переменной и ее захват.

Не генерируется ошибка operator(), это чистые указатели на соответствие подписи, подразумеваемые чистой безъядерной лямбдой.