A diferença de duas instâncias constexpr de ponteiros __func__ ainda constexpr?

14

Isso é válido em C ++?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

O GCC e o MSVC acham que está tudo bem, Clang acha que não: o Compiler Explorer .


Todos os compiladores concordam que este está correto: Explorer do compilador .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

Clang novamente não gosta deste, mas os outros estão bem com ele: Explorador de Compiladores

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

O que há aqui em cima? Eu acho que a aritmética em ponteiros não relacionados é um comportamento indefinido, mas __func__retorna o mesmo ponteiro, não? Não tenho certeza, então pensei em testá-lo. Se bem me lembro, std::equal_topode comparar ponteiros não relacionados sem comportamento indefinido:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Clang acha que eq(__func__, __func__)não é uma expressão constante, mesmo que std::equal_to::operator() seja consexpr . Outros compiladores não reclamam: Compiler Explorer


Clang também não compilará este. Reclama que __func__ == __func__não é uma expressão constante: Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
fonte
De Function_definition , __func__é tão-se static const char __func__[] = "function-name";e que equivale é aceito demo ...
Jarod42
Curiosamente, ele funciona se você inicializar uma variável constexpr com __func__e uso que no static_assert ...
Florestan
@ Jarod42 Então isso é um bug no Clang?
Ayxan
@florestan como este ? Também não será compilado com o Clang. Meus segundo e terceiro exemplos na pergunta são da maneira que você mencionou. Um compila, o outro não.
Ayxan
11
Consulte também CWG1962 , que pode ser removido __func__inteiramente da avaliação constexpr.
Davis Herring

Respostas:

13

__func__em C ++ é um identificador. Em particular, ele faz referência a um objeto específico. De [dcl.fct.def.general] / 8 :

A variável predefinida local da função _­_­func_­_­é definida como se uma definição do formulário

static const char __func__[] = "function-name";

foi fornecido, em que nome da função é uma sequência definida pela implementação. Não é especificado se essa variável possui um endereço distinto do de qualquer outro objeto no programa.

Como uma variável predefinida local da função , essa definição (como se) aparece no início do bloco da função. Como tal, qualquer uso __func__dentro desse bloco se referirá a essa variável.

Quanto à parte "qualquer outro objeto", uma variável define um objeto. __func__nomeia o objeto definido por essa variável. Portanto, dentro de uma função, todos os usos do __func__nome da mesma variável. O que é indefinido é se essa variável é um objeto distinto de outros objetos.

Ou seja, se você estiver em uma função nomeada fooe tiver usado o literal em "foo"algum outro lugar do problema, não é proibido que uma implementação faça com que a variável __func__também seja o mesmo objeto que o literal "foo"retorna. Ou seja, o padrão não exige que todas as funções em que __func__aparecem devem armazenar dados separados da própria cadeia de caracteres literal.

Agora, a regra "como se" do C ++ permite que as implementações se desviem disso, mas elas não podem fazer isso de uma maneira que seria detectável. Portanto, enquanto a variável em si pode ou não ter um endereço distinto de outros objetos, os usos de __func__na mesma função devem se comportar como se estivessem se referindo ao mesmo objeto.

Clang não parece implementar __func__dessa maneira. Parece implementá-lo como se retornasse uma string de valor prvalor literal do nome da função. Dois literais de cadeia de caracteres distintos não precisam se referir ao mesmo objeto; portanto, subtrair ponteiros para eles é UB. E o comportamento indefinido em um contexto de expressão constante é mal formado.

A única coisa que me deixa hesitante em dizer que Clang está 100% errado aqui é [temp.arg.nontype] / 2 :

Para um parâmetro-modelo não-tipo de referência ou tipo de ponteiro, o valor da expressão constante não deve se referir a (ou para um tipo de ponteiro, não deve ser o endereço de):

...

  • uma _­_­func_­_variável predefinida .

Veja, isso parece permitir alguns enganos pela implementação. Ou seja, embora __func__tecnicamente possa ser uma expressão constante, você não pode usá-la em um parâmetro de modelo. É tratado como uma string literal, mesmo que seja tecnicamente uma variável.

Então, em algum nível, eu diria que o padrão está falando dos dois lados da boca.

Nicol Bolas
fonte
Então, estritamente falando, __func__pode ser uma expressão constante em todos os casos da minha pergunta, certo? Portanto, o código deveria ter sido compilado.
Ayxan 28/12/19
E sobre o "Não é especificado se essa variável possui um endereço distinto do de qualquer outro objeto no programa". parte? Comportamento não especificado significa não determinismo no comportamento da máquina abstrata. Isso pode ser problemático para a avaliação do constexpr? E se a primeira ocorrência do __func__endereço for igual à de outro objeto, e na segunda ocorrência __func__não for? É verdade que isso não significa que o endereço seja diferente entre as duas instâncias, mas ainda estou confuso!
Johannes Schaub - litb
@ JohannesSchaub-litb: " E a parte" Não é especificado se essa variável possui um endereço distinto do de qualquer outro objeto no programa. "Parte "? __func__não é uma macro; é um identificador que nomeia uma variável específica e, portanto, um objeto específico. Portanto, qualquer uso __func__na mesma função deve resultar em um valor de gl que se refere ao mesmo objeto. Ou, mais precisamente, não pode ser implementado de tal maneira que não seja esse o caso.
Nicol Bolas
@ Nicol que se refere ao mesmo objeto. Mas esse objeto pode em um instante ter o mesmo endereço que outro objeto. E no outro instante não tem. Não estou dizendo que isso é um problema, mas apenas lembrando a todos essa possibilidade. E depois de tudo, também posso estar enganado, então também estou dizendo isso na esperança de ser corrigido ou confirmado.
Johannes Schaub - litb
@ JohannesSchaub-litb: " Mas esse objeto pode, em um instante, ter o mesmo endereço que outro objeto. " Isso não é permitido no modelo de objeto C ++. Dois objetos, nenhum sendo aninhado dentro do outro, não podem estar ambos durante a vida útil no mesmo armazenamento ao mesmo tempo. E o objeto em questão tem duração de armazenamento estático; portanto, a menos que você use o posicionamento new, ele não será levado a lugar nenhum até o término do programa.
Nicol Bolas