Fonctions Constexpr non appelées au moment de la compilation si le résultat est ignoré

Aug 15 2020

J'étudiais des résultats de couverture de code plutôt étranges des constexprfonctions (les appels au moment de la compilation ne peuvent pas être instrumentés par l'outil de couverture de code que j'utilise), et j'ai remarqué que certaines constexprfonctions étaient appelées en tant que fonctions d' exécution , si les résultats de la fonction les appels n’ont pas été enregistrés .

Il semble que, pour les constexprfonctions ou les méthodes, si vous stockez les résultats de l'appel (soit dans une variable d'exécution [accentuation !!!], soit dans une constexprvariable), l'appel est un appel à la compilation (tant que les paramètres sont compilés -temps). Si vous ignorez les résultats, l'appel est un appel d'exécution. Cela n'a rien à voir avec mon outil de couverture de code; le comportement semble être reproductible dans l'exemple simple ci-dessous.

Vous pourriez soutenir que puisque les constexprfonctions ne peuvent pas avoir d'effets secondaires, peu importe ce que fait le compilateur si vous ne retournez / n'utilisez pas de résultat. Je pense que pour l'efficacité, le compilateur ferait toujours ce qu'il peut être constexpr, mais ce n'est ni ici ni là ... Ce que je me demande, c'est si c'est même un comportement défini.

Est-ce un moyen portable de garantir que vos constexprfonctions seront appelées en tant que runtime ??? Il n'y a pas une tonne d'utilisations pour cela, mais une utilisation est: si vous voulez "prendre le crédit pour les tests qui ont été appelés sur des constexprfonctions" dans la couverture de code, en appelant simplement la même fonction à la fin de votre test unitaire, et ignorant le résultat, afin qu'ils soient instrumentés.

Il existe d'autres moyens de forcer une fonction à être appelée en tant que runtime, je sais, je suis surtout curieux de savoir ce qui se passe ici. C'était très inattendu quand je l'ai vu pour la première fois. À moins que ce ne soit portable, j'appellerai probablement mes constexprméthodes via un objet d'exécution (ce qui semble faire l'affaire même pour les méthodes statiques), ou via un pointeur de fonction d'exécution.

Voir l'exemple ci-dessous. Démo en direct:https://onlinegdb.com/rJao0RNGP

// Modified from https://stackoverflow.com/a/40410624/12854372

extern bool no_symbol;

struct ContextIsConstexpr {
    size_t s;

    constexpr ContextIsConstexpr() : s(1) {}
    
    constexpr void operator()() {
        auto ignore = s ? 1 : no_symbol;
    }
};

constexpr bool tryIgnoringResult()
{
    ContextIsConstexpr()();
    return true;
}

constexpr void thereIsNoResult() {
    ContextIsConstexpr()();
}

int main()
{
    constexpr auto result1 = tryIgnoringResult(); // OK, compile-time
    auto result2 = tryIgnoringResult(); // OK, compile-time

    // tryIgnoringResult(); // Unexpected, runtime!
    // thereIsNoResult(); // Unexpected, runtime!
}

Réponses

Jarod42 Aug 15 2020 at 14:03

Cela peut être déroutant, mais les fonctions constexpr ne doivent être appelées au moment de la compilation que dans les contextes constexpr (affectation à la variable constexpr, utilisation pour la taille du tableau ou le paramètre de modèle, ...).

Dans un contexte normal, les fonctions sont appelées au moment de l'exécution. L'optimiseur peut résoudre cette fonction au moment de la compilation (comme pour toute autre fonction suivant la règle as-if). constexprest en effet un bon indice pour que l'optimisation se produise.

Vous pourriez soutenir que puisque les fonctions constexpr ne peuvent pas avoir d'effets secondaires

Ils peuvent avoir des effets secondaires, voir l'exemple valide suivant:

constexpr int f(int i)
{
    if (i == 0) return 0;
    std::cout << i << std::endl;
    return i;
}

int main()
{
    [[maybe_unused]] constexpr int zero = f(0); // Compile time
    f(42); // runtime
}

Démo