Может ли лямбда иметь связь "С"?
Название более или менее говорит обо всем. У меня есть следующий бит
кода:
#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()
, это чистые указатели на соответствие подписи, подразумеваемые чистой безъядерной лямбдой.