26

I am playing around with some toy code using c++11 to figure out a bit more about how things work. During this I came across the following issue that simplifies down to:

template <int x, int y>
class add {
public:
    static constexpr int ret = x + y;
};

constexpr int addFunc(const int x, const int y) {
    return add<x,y>::ret;
}

int main() {
    const int x = 1;
    const int y = 2;
    cout << add<x,y>::ret << endl; // Works
    cout << addFunc(1,2) << endl;  // Compiler error
    return 0;
}

I'm using GCC 4.8.1 and the output is:
'x' is not a constant expression in template argument for type 'int'
'y' is not a constant expression in template argument for type 'int'

What exactly is the difference between the two ways I am trying to calculate add::ret? Both of these values should be available at compile time.

Danny
  • 2,221
  • 2
  • 18
  • 21
  • 3
    `constexpr` functions have to be able to be run at runtime. – chris Oct 27 '14 at 07:41
  • 3
    Well... yeah. So why specifically isn't this able to be evaluated at compile time? – Danny Oct 27 '14 at 07:43
  • 7
    To elaborate: `constexpr` functions have to be able to be run at runtime, and your `constexpr` function would fail whenever called with any value that is not a compile-time constant, so your `constexpr` function isn't valid. What you're looking for isn't what `constexpr` provides, and isn't something C++ provides in another form either. What comes closest is making `addFunc` a template function with `int x` and `int y` template parameters. –  Oct 27 '14 at 07:46
  • 1
    Oh okay so basically despite being evaluated at compile time, they must be valid at runtime as well? – Danny Oct 27 '14 at 07:48
  • 1
    haha doing something like this was essentially trying to find a way to avoid using macros. I guess that is how it is going to have to be. – Danny Oct 27 '14 at 07:49
  • I wanted to create a factory for std::array that could be called like `auto a = arrayFactory(n)`. It took a while before realizing the problem boils down to this and there's no pretty alternative to `auto a = arrayFactory()`. – arthropod May 07 '16 at 22:38

4 Answers4

13

You tell the compiler, that addFunc would be a constexpr. But it depents on parameters, that are not constexpr itself, so the compiler already chokes on that. Marking them const only means you are not going to modify them in the function body, and the specific calls you make to the function are not considered at this point.

There is a way you can make the compiler understand you are only going to pass compile time constants to addFunc: Make the parameters a template parameters itself:

template <int x, int y>
constexpr int addFunc() {
    return add<x,y>::ret;
}

Then call as

cout << addFunc<1,2>() << endl;
Raoul Steffen
  • 527
  • 4
  • 10
12

If your purpose is just to shorten code a bit, in C++14 you can create variable template:

template <int x, int y>
constexpr int addVar = x + y;

cout << addVar<5, 6> << endl; // Works with clang 3.5, fails on GCC 4.9.1

GCC 5 will also support this.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
8

The compiler does not know if x and y are always available at compile time as constant values (expression), and what more, C++11/14 does not support constexpr function parameter, so there's no way x and y can be used as parameter for the template add<> in addFunc.

fjanisze
  • 1,234
  • 11
  • 21
5

Function parameters of a constexpr function aren't constant expressions. The function is constexpr to the outside (as calling it might result in a constant expression), but calculations inside are just as constexpr as they would be in a normal function.

Template-arguments require constant expressions. These are the crucial requirements for constant expressions that aren't met in your code and thus produce the compiler error ([expr.const]/2, emphasis mine):

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2) […]:

— an lvalue-to-rvalue conversion (4.1) unless it is applied to

  • a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
  • a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
  • a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;

You are applying an lvalue-to-rvalue conversion on the parameters to pass them as template arguments.
The first bullet item doesn't apply as the function parameter is neither precedingly initialized nor known to be initialized with a constant expression, and the second and third don't either (in particular, function parameters shall not be declared constexpr).

Columbo
  • 60,038
  • 8
  • 155
  • 203