Imagine a seguinte situação, onde eu tenho uma noexcept
função livre em C++ que é passada como um ponteiro de função para outra função com ligação C:
extern "C" {
void some_fun(void(*)());
}
void my_fun() noexcept { /* do stuff */; }
int main()
{
some_fun(my_fun);
}
Isso está correto e/ou é permitido? O que acontece nesse caso? Qual é a semântica aqui considerando que C++17 noexcept
pertence ao tipo de uma função?
Além disso, e para minha surpresa, isso ainda compila (com gcc e clang):
extern "C" {
void some_fun(void(*)() noexcept);
}
void my_fun() noexcept { /* do stuff */; }
int main()
{
some_fun(my_fun);
}
Faz sentido que um ponteiro de função na assinatura de uma função C permita o qualificador noexcept
nela?
Isso não é permitido no C++ padrão. Veja também: `extern "C"` é parte do tipo de uma função? ,
std::qsort
. O parâmetro tosome_fun
é declarado como um "ponteiro para uma função C", e não há conversão padrão de "ponteiro para uma função C++" para "ponteiro para função C".As funções C podem ser
noexcept
. Por exemplo,extern "C" void my_fun() noexcept { /* do stuff */; }
seria uma função C, e você pode passar um ponteiro para ela parasome_fun
. (Há uma conversão de função implícita para remover onoexcept
)GCC, Clang e MSVC pelo menos não distinguem entre funções C e C++, nem mesmo registrando-as como parte do tipo, permitindo sua compilação.
Encontrei um único exemplo de um compilador que não aceita isso: Como você declara um ponteiro de função externo "C"
Está correto e permitido. O que acontece é que há uma sequência de conversão implícita de
void(*)() noexcept
paravoid(*)()
(mas não o contrário). Essa conversão converterá o ponteiro de função para o tipo esperado semnoexcept
. A função pode ser chamada por meio do ponteiro semnoexcept
sem nenhuma restrição.Não há exigência de que os tipos usados em uma declaração de função com
"C"
vinculação sejam tipos C. Do ponto de vista de C++, não há diferença para o comportamento normal, exceto que o nome da função ignora namespaces e não pode ser sobrecarregado (ou seja, do ponto de vista típico de implementação, a manipulação de nomes é desabilitada).No entanto, o que tal declaração significa para a interoperabilidade real com código C é definido pela implementação. Claro que você não seria capaz de declarar ou definir a função com a mesma assinatura em C. Se você alterou a assinatura para a parte C do programa, você realmente deve ter certeza sobre a maneira específica na qual seu compilador compila o código para garantir que haja um comportamento bem definido.
Como a outra resposta de @Artyer aponta, de acordo com o padrão, a ligação de linguagem de uma função é parte de seu tipo e o parâmetro de função na função declarada com
"C"
ligação herda essa ligação de linguagem.my_fun
não é uma função com"C"
ligação e, portanto, não pode ser passada como um ponteiro para uma função com"C"
ligação. Suponho que isso tenha a intenção de permitir diferentes convenções de chamada entre C e C++. Em todos os casos, estou ciente de que os compiladores ignoram essa parte do padrão e consideram a ligação de linguagem não parte do tipo.