Considérez le code suivant:

template<int value>
constexpr int foo = value;

template<typename... Ts>
constexpr int sum(Ts... args) {
    return foo<(args + ...)>;
}

int main() {
    static_assert(sum(10, 1) == 11);
}

Clang 4.0.1 me donne l'erreur suivante:

main.cpp:6:17: error: non-type template argument is not a constant expression
    return foo<(args + ...)>;
                ^~~~

Cela m'a surpris. Chaque argument est connu au moment de la compilation, sum est marqué comme constexpr, donc je ne vois aucune raison pour laquelle l'expression fold ne peut pas être évaluée au moment de la compilation.

Naturellement, cela échoue également avec le même message d'erreur:

constexpr int result = (args + ...); // in sum

[expr.prim.fold] n'est pas très utile, il est très court et décrit uniquement la syntaxe autorisée.

Essayer de nouvelles versions de clang donne également le même résultat, tout comme gcc.

Sont-ils réellement autorisés ou non?

17
Rakete1111 1 août 2017 à 09:51

2 réponses

Meilleure réponse

Une expression constante est autorisée à contenir une expression de repli. Il n'est pas autorisé d'utiliser la valeur d'un paramètre de fonction, sauf si l'appel de fonction fait lui-même partie de l'expression de constante entière. A titre d'exemple:

constexpr int foo(int x) {
    // bar<x>();  // ill-formed
    return x;  // ok
}
constexpr int y = foo(42);

La variable y doit être initialisée avec une expression constante. foo(42) est une expression constante acceptable car même si l'appel de foo(42) implique d'effectuer une conversion lvalue-to-rvalue sur le paramètre x afin de renvoyer sa valeur, ce paramètre a été créé dans l'expression de constante entière foo(42) afin que sa valeur soit statiquement connue. Mais x lui-même n'est pas une expression constante dans foo. Une expression qui n'est pas une expression constante dans le contexte où elle se produit peut néanmoins faire partie d'une expression constante plus large.

L'argument d'un paramètre de modèle non-type doit être une expression constante en soi, mais x ne l'est pas. La ligne commentée est donc mal formée.

De même, votre (args + ...) ne peut pas être une expression constante (et ne peut donc pas être utilisé comme argument de modèle) car il effectue une conversion de lvaleur en rvalue sur les paramètres de sum. Cependant, si la fonction sum est appelée avec des arguments d'expression constants, l'appel de fonction dans son ensemble peut être une expression constante même si (args + ...) y apparaît.

12
Brian 1 août 2017 à 07:20

Certains lecteurs de cette question pourraient être intéressés de savoir comment l'exemple OP: s pourrait être modifié afin de compiler et de fonctionner comme prévu, c'est pourquoi j'inclus cet addendum au Brian: excellente réponse acceptée.

Comme Brian le décrit, la valeur du paramètre de fonction variadique n'est pas une expression constante dans sum (mais ne fera pas que foo ne soit pas une expression constante tant que le paramètre "n'échappe" pas le portée de foo; telle qu'elle a été créée dans l'expression constante foo(42)).

Pour appliquer ces connaissances à l'exemple OP: s, au lieu d'utiliser un paramètre de fonction variadique qui ne sera pas traité comme un constexpr lors de l'échappement de la constexpr portée immédiate de sum, nous pouvons migrer le paramètre de fonction variadique comme paramètre de modèle non-type variadique.

template<auto value>
constexpr auto foo = value;

template<auto... args>
constexpr auto sum() {
    return foo<(args + ...)>;
}

int main() {
    static_assert(sum<10, 1, 3>() == 14);
}
5
dfri 1 août 2017 à 08:02