1

Trying to compile the following using c++11 standards (using constexpr) completed successfully:

class test{
 public:
 int getId(){
   constexpr int id = 5;
   return id;
 }
};

During compile time, test doesn't exist yet, however, the above code compiles just fine. If test doesn't exist yet then how can getId exist during compile time?

Full working example:

#include <iostream>

class test{
 public:
 int getId(){
   constexpr int id = 5;
   return id;
 }
};

int main(){
 test t;
 std::cout << t.getId() << std::endl;
 return 0;
}
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • In the above code, `getId` needn't be compiled at all — it's unreachable code. It'll be parsed though, of course. What sort of existence are you referring to with "how can `getId` exist during compile time?"? – Tommy Dec 09 '19 at 02:13
  • well since I didn't have to add `static` to my constexpr i thought the function might be available during compile time somehow. so then it's not? –  Dec 09 '19 at 02:17
  • Why would an `inline` function be ok here? i'm still trying to evaluate `id` during compile time where the class itself doesn't exist yet. –  Dec 09 '19 at 02:18
  • 3
    Please expand on what you mean by "available at compile time". The function itself is not `constexpr`, just a variable in the function. – user4581301 Dec 09 '19 at 02:19
  • constexpr means "evaluate during compile time" right? if I want to evaluate `id` during compile time, then what does `id` belong too? it's not static so it should belong to its parent function `getId` which belongs to its parent class `test`. `id` doesn't exist on its own, it doesn't exist until the class is initialized which will initialize the functions? right? thats where my confusion is. If `getId` doesn't exist until runtime then how can a variable under it exist during compile time? –  Dec 09 '19 at 02:22
  • 3
    @Josh No, `constexpr` means "this *can be* evaluated during compile time". Things marked `constexpr` can *also* be evaluated at runtime. – JaMiT Dec 09 '19 at 02:40
  • Ah so in my full example above, `constexpr` is just ignored? –  Dec 09 '19 at 02:41
  • No. the constexpr is not ignored. id is evaluated at compile time (since that is the only part you wanted to be made constexpr, what do you expect to happen?). getId and 'test t' are not constexpr, so they aren't ignoring constexpr, they were never constexpr to begin with. – robthebloke Dec 09 '19 at 02:56
  • So in your example, getId is a runtime method (and not compile time). In the case of inline methods, the compiler may be able to see through that and evaluate the full lot at compile time, but that's not guaranteed. – robthebloke Dec 09 '19 at 02:58
  • I'm expecting the compiler to throw an error and not even compile. What does it mean to say `id is evaluated at compile time`? how can it evaluate `id` during compile time if `getId` (the parent) is only evaluated during run time. –  Dec 09 '19 at 03:01
  • '5' doesn't need to be evaluaated. If you change id to be: "constexpr int id = 5 + 5 * 6 - 22 / 11;" The constexpr would still evaluate at compile time. It just so happens '5' is not exactly an interesting expression. – robthebloke Dec 09 '19 at 03:06
  • Related: [Difference between `constexpr` and `const`](https://stackoverflow.com/questions/14116003/difference-between-constexpr-and-const). This has some useful information, but it might fall a bit short of being a true duplicate. – JaMiT Dec 09 '19 at 06:39

3 Answers3

0

You can use the member function as constexpr, but you must declare it as constexpr.

https://godbolt.org/z/P2fcfK

#include <iostream>

class test{
 public:
 constexpr int getId(){
   constexpr int id = 5;
   return id;
 }
};

int main()
{
    constexpr int G = test().getId();
    std::cout << G << std::endl;
    return 0;
}

/edit This is the code you've posted....

#include <iostream>

class test{
 public:
 int getId(){ //< this method is NOT constexpr
   constexpr int id = 5; //< id is constexpr, and it's the only constexpr here.
   return id;
 }
};

int main(){
 test t; //< this cannot be constexpr because it's a variable
         //< (which means it is NOT an expression!)
 std::cout << t.getId() << std::endl;
 return 0;
}

\edit2 This is the simplest possible expression that can be evaluted at compile time:

constexpr int id = 5;

This is an expression that's slightly more interesting

constexpr int id = 5 * 5 + 22 / 11 - 9 * 65;

In that case, the expression can be evaluated at compile time. This is an example of something that cannot be evaluated at compile time:

int var(int a) {
  constexpr int id = 5 * a; //< invalid! a is not constexpr!
  return id;
}

getId() cannot be constexpr here, because we need to declare it as constexpr, however a naive approach will fail:

constexpr int var(const int a) {
  constexpr int id = 5 * a; //< invalid! a is not constexpr!
  return id;
}

It fails because we are using the variable 'a' in a second expression. However, this will work (we have removed the second expression, and folded it back into a single expression)

constexpr int var(const int a) {
  return 5 * a;
}

constexpr int foo = var(22); //< will be evaluated at compile time. 

Now we could do this with member variables and a ctor, but we need to ensure the ctor is constexpr. So for example:

#include <iostream>

struct Dave 
{
  const int a;

  constexpr Dave(const int b) : a(b) {}

  constexpr int var() const {
    return 5 * a;
  }
};

int main()
{
    constexpr int c = Dave(22).var();
    return c;
}

Now looking at godbolt: https://godbolt.org/z/4naDY3 We can see that the code for main boils down to:

main:
        mov     eax, 110
        ret

So it has evaluated at compile time 5 * 22, which is 110. You might not like the way the constexpr rules work (they are a little clunky), but constexpr does work.

robthebloke
  • 9,331
  • 9
  • 12
  • It is true, but you're not doing it right. You can ONLY use constexpr's within an expression. Declaring test as a variable means it is not an expression, which means it cannot be constexpr. However, if you declare a temp within an expression (hence test() in my code above), then it works. – robthebloke Dec 09 '19 at 02:44
  • so in my full example above, constexpr is just ignored? –  Dec 09 '19 at 02:45
  • you have not declared getId as constexpr. Expressions must evaluate on a single line. Moving the test variable out of the expression evaluation (to the line above) breaks constexpr. The example I have written above, constexpr evaluates all the way up to G. Go and look closer at what I'm doing (evaluating G as constexpr), vs what you are doing (evaluating id as constexpr only). – robthebloke Dec 09 '19 at 02:47
  • Right right, but my question is, although the way that i'm doing it is wrong, what does it mean? Why isn't the compiler complaining? it is just ignoring it at that point? –  Dec 09 '19 at 02:49
  • It's doing exactly what you asked of it. id is constexpr, and literally nothing else. So constexpr is working here. It just so happens that none of the rest of the code is constexpr. – robthebloke Dec 09 '19 at 02:54
  • ok so here is the final question, it's doing what Im asking and marks id as `constexpr`, although it's not static. So then what does `id` belong too? during compilation it says "ok we have a variable called id with the value 5" but during compilation the class `test` doesn't even exist yet. it only comes to life during run time. how is it even compiling. –  Dec 09 '19 at 02:58
  • id doesn't belong anywhere, it'll just be duplicated everywhere. The x64 instructions prefer to load constants from 8bit offsets from the current instruction (or the stack frame register). To do this is cheaper than loading a constant with a 32bit offset (smaller machine code basically). The compiler will try to insert than constant + or - 128 bytes from the instruction location. This means it will copy it numerous times when it is used. static implies a variable that can be changed, which implies there must be one copy. constexpr does not work like that. – robthebloke Dec 09 '19 at 03:05
  • @Josh in this case your example is too trivial to see the full power of `constexpr`. All that's likely to happen is `getId` will compile into `return 5;` and [any decent compiler would do that anyway](https://godbolt.org/z/J5cgHk). – user4581301 Dec 09 '19 at 04:23
0

The source of your confusion is probably thinking of "constexpr" as "evaluated at compile time". While there is truth to this idea, it is imprecise. I would invite you to drop your current understanding of "constexpr". Instead, start thinking of "constexpr" as "allowed in constant expressions". The language provides a precise definition for "constant expression"; for present purposes, it might be enough to consider a constant expression to be an expression whose value can be computed by the compiler. (If the compiler computes that value, it would need to evaluate something at compile time. This leads to that imprecise notion of constexpr.)

As you adjust your thinking to this new view, focus on the word "allowed". A person with a driver's license is allowed to drive a car but is not prohibited from riding a bicycle. A variable with the constexpr specifier is allowed in constant expressions but is not prohibited from other expressions.

Warning: When dealing specifically with integer variables, the constexpr specifier syntactically does not mean much more than const. If an integer variable is both declared const and initialized by a constant expression, then that variable is given the same privilege as a constexpr—it is allowed in constant expressions. This is a limited special case that supports pre-C++11 code. To clearly express intent (semantics) in new code, any variable used in a constant expression should be constexpr even if the language syntax allows it to be merely const.

During compile time, test doesn't exist yet, however, the above code compiles just fine.

Yes, code that has no syntax errors does tend to compile just fine. ;)

For variables, the only limitation imposed by constexpr that is not imposed by const is that the variable's initialization must be a constant expression (i.e. the value must be something the compiler can compute). You satisfied that by initializing id to 5 rather than to some value determined at run time. Your use of id after initialization is consistent with the use of a const int, so that use is valid for a constexpr int.

If test doesn't exist yet then how can getId exist during compile time?

I guess that depends on what you mean by "exist". In one sense, a function exists from the moment the compiler generates its object code. That code gets incorporated into the final executable. The object code does not need to be constructed during run time (only loaded from disk), so one could say the function started existing before the program was run. There are caveats, but the general idea is valid.

In a similar way, one could say that the class test exists during compilation, but I suspect you meant that instantiations (objects) of test don't exist yet.

If you had another meaning for "exist" in mind, then your claim that getId exists during compile time is possibly false. (In particular, getId is not executed during compilation.)

[From a comment:] What does it mean to say id is evaluated at compile time?

Correction: id can be evaluated at compile time. It can also be evaluated at run time. What actually happens depends on how id is used. If you do not give the compiler a reason to evaluate id, it does not have to.

[From a comment:] how can it evaluate id during compile time if getId (the parent) is only evaluated during run time.

The only place id is used in your code is in a return statement. The compiler is free to replace return id; with return 5; when it generates the object code for getId. (Not a particularly interesting example, but technically an evaluation of id at compile time. A more interesting example would use id in a context that requires a constant expression.)

JaMiT
  • 14,422
  • 4
  • 15
  • 31
0

During compile time, test doesn't exist yet, however, the above code compiles just fine. If test doesn't exist yet then how can getId exist during compile time?

What if I tell you that this code might work even if test object didn't exist in realtime? I bet this program will print you 5:

int main(){
 test* t = nullptr;
 std::cout << t->getId() << std::endl;
 return 0;
}

Sure, you should never write code like this, but it can help you to get some understanding of what member function actually is.

You can think of a member function as a regular function with an object state (member variables) passed into it.

class test{
 public:
};

int getId(test* obj){
  constexpr int id = 5;
  return id;
}

See, an object state is not used inside getId() function. So, you don't need a test object instance to run getId() at all.

Speaking of constexpr

constexpr int id = 5;

This is just a modern way of writing named constant... You can use macro or enum to get the same result, and you don't need C++11 or even C++ (C is enough).

 int getId(){
   enum { id = 5 };
   return id;
 }

 int getId(){
   #define id 5
   return id;
 }

So, from a compiler perspective of view, getID() is just a function that returns 5. That means it can be replaced with equivalent integer constant 5. So, it will actually insert 5 instead of getId() call.

Stas
  • 11,571
  • 9
  • 40
  • 58