0

The code compiles and runs, but I'm trying to understand how the expression (obj.*funcPtr)(12) can be evaluated at compile-time when obj is not declared as constexpr. I would expect that this might not meet the standard requirements for compile-time evaluation.

#include <iostream>
#include <stdexcept>
template <typename T, size_t N>
class Array
{
public:
 T& operator[](size_t i)
 {
 if (i>=N)
 {
 throw std::out_of_range("Bd index!");
 }
 return data_[i];
 }
 constexpr size_t Size() const
 {
 return N;
 }
private:
 T data_[N]{};
};
class MyClass
{
public:
 constexpr int myFunction(int value)
 {
 return value*2;
 }
};
int main()
{
 constexpr int (MyClass::*funcPtr)(int) = &MyClass::myFunction;
 MyClass obj;
 Array<int , (obj.*funcPtr)(12) > arr;
 std::cout << arr.Size() << '\n';
}
Richard
47.7k7 gold badges42 silver badges96 bronze badges
asked Jul 21, 2024 at 16:36
2
  • You can reduce your program. See reduced example Commented Jul 21, 2024 at 16:40
  • It works because myFunction does not depends on any member of MyClass obj. If you add dependency on such member it will fail to compile as you expected. Commented Jul 21, 2024 at 16:40

1 Answer 1

3

The call to the function is a constant expression, because none of the items of [expr.cont]/5 disqualify it from being a constant expression. In particular you are not attempting to perform any problematic lvalue-to-rvalue conversion in the meaning of item 5.9. The only lvalue-to-rvalue conversion you perform is on value, whose lifetime started during the constant expression evaluation, and funcPtr, which is explicitly marked constexpr.

For an implementation point of view: Your function myFunction doesn't access any data member of obj. So the compiler can determine its result at compile-time from the given argument and doesn't need to care at all about the actual obj instance. Its state has no effect on the result of the function call. The result is always 24.

answered Jul 21, 2024 at 16:42
Sign up to request clarification or add additional context in comments.

5 Comments

if you change the obj to pointer, gcc/clang won't consider the call through pointer a constexpr anymore, even though from the implementation point of view it's the same (not sure about the standard). MSVC dissented and compiled it. I wonder which one is correct. See: godbolt.org/z/c1zx1cadE
@Gene Dereferencing a null pointer (the *obj part) is explicitly undefined behavior and undefined behavior disqualifies an expression from being a (core) constant expression per item [expr.const]/5.8. That dereferencing the pointer itself causes UB, even if the result is unused, has been clarified relatively recently by CWG 2823, although it was always UB to call a non-static member function on a null pointer. MSVC is wrong.
@Gene Also, it is necessary to know the actual value of the pointer in this case, i.e. removing constexpr from the pointer won't work, because *obj requires an lvalue-to-rvalue conversion on obj. (The built-in * can only be applied to prvalues.)
@Gene Of course, from the implementation point-of-view it wouldn't be any problem to allow this anyway, but here the standard has made the explicit decision to consider the construct undefined (either because semantically it shouldn't be possible to call member functions on non-existent object or because it permits more optimization in non-static member functions).
yes, it looks like nullptr is the actual culprit, not the call via the pointer. Fixing that every compiler works: godbolt.org/z/E74bYvonh

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.