33

The following program compiles:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage

int main()
{
    Test<&var> test;
}

This one, however, doesn't, which is a surprise for me:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage
extern const int * const ptr = &var; //extern needed to force external linkage
int main()
{
    Test<ptr> test; //FAIL! Expected constant expression.
}

Alternative example:

int main()
{
   const int size = 42;
   int ok[*&size]; //OK

   const int * const pSize = &size;
   int fail[*pSize]; //FAIL
}

I have concluded that a pointer just can't be a constant expression regardless of whether it's const and initialized with a constant expression.

Questions:

  1. Is my conclusion true?
  2. If so, why can't a pointer be a constant expression? If not, why don't the above programs compile?
  3. Does C++0x(C++11, if you will) change anything?

Thanks for any insights!

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 2
    GCC gives the following error: "‘ptr’ is not a valid template argument because ‘ptr’ is a variable, not the address of a variable" This seems to indicate that pointer template arguments have to be actual results of address-of...? – Kerrek SB Sep 12 '11 at 18:18
  • @Kerrek SB: See my second example - it seems that pointers cannot be constant expressions... somehow... inexplicably – Armen Tsirunyan Sep 12 '11 at 18:20
  • Somehow the GCC error is more specific, though. It's not just that `ptr` is not a constant expression (which it actually is), but the fact that it is a variable. No idea where that's covered in the standard, though. – Kerrek SB Sep 12 '11 at 18:24
  • @Kerrek SB: My suspicion is that it isn't explicitly covered. It's one of the things that we were supposed to deduce :) Anyway, I am more interested in the ***why*** aspect. I mean, obviously, the value of the pointer is a constant expression, but the pointer itself isn't. What impediments were there to allow this? – Armen Tsirunyan Sep 12 '11 at 18:26

3 Answers3

14

It's a bit more complicated. In C++03 and C++11, &var is a constant expression if var is a local static / class static or namespace scope variable. This is called an address constant expression. Initializing a class static or namespace scope pointer variable with that constant expression is guaranteed to be done before any code is run (static initialization phase), because of it being a constant expression.

However only since C++11, a constexpr pointer variable that stores the address &var can also be used as an address constant expression and only since C++11, you can dereference an address constant expression (actually, you can dereference even more - even local array element addresses, but let's keep it ontopic) and if it refers to a constant integral variable initialized prior to the dereference or a constexpr variable, you again get a constant expression (depending on the type and value category, the kind of constant expression may vary). As such, the following is valid C++11:

int const x = 42;
constexpr int const *px = &x;

// both the value of "px" and the value of "*px" are prvalue constant expressions
int array[*px];
int main() { return sizeof(array); }

If so, why can't a pointer be a constant expression? If not, why don't the above programs compile?

This is a known limitation in the Standard's wording - it currently only allows other template parameters as arguments or & object, for a template parameter of pointer type. Even though the compiler should be capable of doing much more.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
2

It's still not allowed in C++0x. temp.arg.nontype requires:

A template-argument for a non-type, non-template template-parameter shall be one of:

  • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
  • the name of a non-type template-parameter; or
  • a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
  • a constant expression that evaluates to a null pointer value (4.10); or
  • a constant expression that evaluates to a null member pointer value (4.11); or
  • a pointer to member expressed as described in 5.3.1.

original answer:

  1. In C++03, only integral expressions can be constant expressions.
  2. Because the standard says so (naturally).
  3. In C++0x, n3290 includes examples using constexpr on a pointer. So what you are trying should now be possible, although you must now use the constexpr keyword instead of top-level const.

There's also a gcc bug involved, g++ rejects the standard draft's own examples of valid constexpr usage.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    I actually tried all this with `constexpr`, with the same results as in my other comments... – Kerrek SB Sep 12 '11 at 18:27
  • 1) &var isn't integral, is it? 2) You mean "I don't know" 3) What about @Kerrek SB's comment? – Armen Tsirunyan Sep 12 '11 at 18:27
  • @Kerrek: gcc doesn't implement C++0x fully in 4.5.1. Do you have a newer version to test with? – Ben Voigt Sep 12 '11 at 18:34
  • 1
    I'm testing with 4.6.1. We should ask someone who has the snapshot version, but the error message seems pretty specific and deliberate, so I'd be surprised if this is just wrongly implemented. – Kerrek SB Sep 12 '11 at 18:36
  • @Armen: I don't think `&var` is a constant expression in C++03. Actual template parameters have to fit in one of several categories, constant expressions are one and pointers are another. C++0x merges these concepts. – Ben Voigt Sep 12 '11 at 18:37
  • @Kerrek: Would you try the code at http://ideone.com/hSPrJ? It is copied directly from n3290, bottom of page 127. – Ben Voigt Sep 12 '11 at 18:38
  • @Ben: `Test` gives the same error about `xp` being a variable. `Test<&x>` fails `because ‘x’ does not have external linkage`. `Test` fails with `could not convert template argument ‘addr((* & x))’ to ‘const int*’` (The three declarations themselves are fine.) – Kerrek SB Sep 12 '11 at 18:44
  • @Kerrek: And what if you gave `x` external linkage? – Ben Voigt Sep 12 '11 at 19:06
  • @Ben: with `extern const int x = 5;`, `Test<&x>` works. But that's just going back to your very first example, I suppose. `addr(x)` still doesn't work. – Kerrek SB Sep 12 '11 at 19:20
1

The problem is because your C++ program can be loaded at any point in memory, and so the address of a global var may be different each time you run the program. What happens if you run your program twice? var is obviously in two different locations then.

Even worse, in your example, you take the address of a variable on the stack! look at this:

void myfunction( unsigned int depth) {
     const int myvar = depth;
     const int * const myptr = &myvar;
     if (depth)
         myfunction(depth-1);
}

If main calls myfunction(3), then 3 myvars are created at seperate locations. There's no way for the compile time to even know how many myvars are created, much less there exact locations.

Finally: declaring a variable to be const means: "I promise", and does not mean that is a compile time constant. See this example:

int main(int argc, char** argv) {
    const int cargc = argc;
    char* myargs[cargc]; //the size is constant, but not a _compile time_ constant.
}
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 1
    I know the difference between const and constant expression. Your answer contradicts the well-formed part of my second example. And also it contradicts the first well-formed example. – Armen Tsirunyan Sep 12 '11 at 19:03
  • 1
    Hmmm... I'm not sure I believe that. After all, `Test<&var>` *does* work, despite `var`'s alleged uncertain location. – Kerrek SB Sep 12 '11 at 19:03
  • 1
    The pointer is constant and initialized at compile-time. The value you see in a pointer is not absolute, but an offset from an absolute address the operating system knows. Even if the program runs twice at the same time the pointers can have the same value yet in reality point to two different addresses. – wilhelmtell Sep 12 '11 at 19:04
  • Values in a pointer are absolute virtual addresses. Multiple simultaneous instances of the program are allowed through the magic of virtual->physical address translation; each process has its own translation table. – Ben Voigt Sep 12 '11 at 19:07
  • Ah right, forgot about virtual memory. Well, there's another completely wrong answer for me. @Kerrek SB: that's a very solid proof. – Mooing Duck Sep 12 '11 at 19:07
  • @Mooing: Although, you make a good point about the last snippet in the example -- taking the address of a non-static local variable, even in `main`, cannot yield a constant expression. I think the g++ extension allowing VLAs even in C++ mode is in play here. – Ben Voigt Sep 12 '11 at 19:10
  • @Armen: Even if it does compile, it _shouldn't_ compile. Many OS's will change the place in virtual memory where programs are loaded. Even static memory addresses will change. It is _impossible_ for the compiler to know at compile time what the virtual address of any variable will be. – Nicol Bolas Sep 12 '11 at 19:39
  • @Nicol Bolas: I have no idea how it's implemented, but I am pretty sure my ***first*** example is well-formed. Not 100% sure about the second one, though – Armen Tsirunyan Sep 12 '11 at 19:41
  • @Nicol Bolas: Indeed, as Johannes's answer suggests, both my "well-formed" examples are legal in C++11. My second example, though, is legal ***only*** in C++11 – Armen Tsirunyan Sep 12 '11 at 22:19