web-dev-qa-db-fra.com

Est-il possible de savoir quand constexpr est vraiment un constexpr?

Depuis les versions étendues de constexpr (je pense de C++ 14), vous pouvez déclarer des fonctions constexpr pouvant être utilisées comme "réelles" constexpr, c'est-à-dire que le code exécuté au moment de la compilation ou peut se comporter comme des fonctions inline. Alors, quand peut avoir ce programme:

#include <iostream>

constexpr int foo(const int s) {
  return s + 4;
}

int main()
{
    std::cout << foo(3) << std::endl;
    const int bar = 3;
    std::cout << foo(bar) << std::endl;
    constexpr int a = 3;
    std::cout << foo(a) << std::endl;

    return 0;
}

Bien entendu, le résultat est:

7
7
7

jusqu'ici tout va bien. Ma question est donc la suivante: existe-t-il un moyen (éventuellement standard) de savoir dans foo (const int s) si la fonction est exécutée à la compilation ou à l'exécution?

EDIT: Est-il également possible de savoir au moment de l'exécution si une fonction a été évaluée au moment de la compilation?

13
LeDYoM

La technique indiquée fonctionne, mais comme elle utilise static_assert, elle n’est pas conviviale. Une meilleure façon (en théorie, vous verrez ce que je veux dire) pour ce faire est de vérifier si une fonction est noexcept. Pourquoi? Parce que les expressions constantes sont toujours noexcept, même si les fonctions ne sont pas marquées comme telles. Alors, considérons le code suivant:

template <class T>
constexpr void test_helper(T&& t) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

test_helper est constexpr, ce sera donc une expression constante aussi longtemps que son argument le sera. Si c'est une expression constante, ce sera noexcept, mais sinon ce ne sera pas (car il n'est pas marqué comme tel). 

Alors maintenant, définissons ceci:

double bar(double x) { return x; }

constexpr double foo(double x, bool b) {
    if (b) return x; 
    else return bar(x);
}

foo est seulement noexcept si x est une expression constante et b est vrai; si le booléen est faux, nous appelons une fonction non constexpr, ce qui ruine notre constexpr-ness. Alors testons ceci:

double d = 0.0;

constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));

std::cerr << x << y << z;

Ca compile, génial! Cela nous donne des temps de compilation (et non des échecs de compilation), qui peuvent être utilisés pour sfinae, par exemple.

La prise? Eh bien, Clang a un bogue de plusieurs années et ne le gère pas correctement. gcc cependant, fait. Exemple en direct: http://coliru.stacked-crooked.com/a/e7b037932c358149 . Il imprime "100", comme il se doit.

13
Nir Friedman

Je pense que la manière canonique de le faire est avec static_assert. static_asserts sont évalués au moment de la compilation, ils casseront donc la construction si leur condition est fausse.

#include <iostream>

constexpr int foo(const int s) {
  return s + 4;
}

int main()
{
    std::cout << foo(3) << std::endl;
    const int bar = 3;
    std::cout << foo(bar) << std::endl;
    constexpr int a = 3;
    std::cout << foo(a) << std::endl;

    static_assert(foo(3) == 7, "Literal failed");
    static_assert(foo(bar) == 7, "const int failed");
    static_assert(foo(a) == 7, "constexpr int failed");
    return 0;
}

clang++ -std=c++14 so1.cpp compile bien pour moi, montrant que tout fonctionne comme prévu.

4
peteigel

Dans une fonction constexpr, vous ne pouvez pas savoir si vous êtes évalué dans un contexte constexpr. Un certain nombre de propositions ont été proposées pour ajouter cette fonctionnalité. Aucun n'a réussi.


En dehors d'une fonction constexpr, il existe plusieurs façons de déterminer si un appel à une fonction avec un certain ensemble d'arguments serait évalué dans un contexte constexpr. Le plus simple serait d’utiliser le résultat dans un contexte nécessitant constexpr.

En supposant que votre expression constexpr retourne un type d'intégrale ou de pointeur non vide (y compris le pointeur de fonction):

#define CONSTEXPR_EVAL(...) \
  std::integral_constant< \
    std::decay_t<decltype(__VA_ARGS__)>, \
    __VA_ARGS__ \
  >::value

alors CONSTEXPR_EVAL( bar(foo, true) ) échouera à la compilation si bar(foo, true) ne peut pas être évalué au moment de la compilation, et s'il peut être évalué au moment de la compilation, il renvoie cette valeur.

D'autres astuces impliquant noexcept (une fonction évaluée à la compilation est noexcept) peuvent fonctionner (voir @ réponse de NirFriedman ).

1

Désolé de gâcher la fête, mais il n’ya certainement pas de moyen standard de le faire. Selon la règle as-if, le compilateur peut émettre un code qui calcule le résultat au moment de l'exécution, même dans les cas où il a déjà été contraint de le calculer à la compilation dans un contexte différent. Tout ce qui peut être fait au moment de la compilation peut être refait au moment de l'exécution, non? Et le calcul a déjà été prouvé pour ne pas lancer.

Par extension, tous les contrôles IS_REALLY_CONSTEXPR ou is_really_constexpr conformes à la norme ne peuvent pas infirmer le fait que le même appel soit exact ou que, par ailleurs, la valeur du même symbole constexpr implique un calcul d’exécution.

Bien sûr, il n’ya généralement aucune raison de répéter un calcul au moment de l’exécution ou même lors de la compilation, mais la question visait à savoir si le compilateur utilise le résultat précalculé, et il n’en existe pas. .

Maintenant, vous avez dit éventuellement standard , votre meilleur choix est donc de tester l'une des solutions fournies avec le compilateur de votre choix et d'espérer qu'il se comporte de manière cohérente. (Ou en lisant le code source s'il s'agit d'une source ouverte/publique et que vous êtes tellement enclin.)

0
Arne Vogel