Код компилируется и запускается, но я пытаюсь понять, как выражение (obj.*funcPtr)(12) можно оценить во время компиляции, когда obj не объявлено как constexpr. Я ожидаю, что это может не соответствовать стандартным требованиям для оценки во время компиляции.
#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';
}
Это работает, потому что myFunction не зависит ни от одного члена MyClass obj. Если вы добавите зависимость от такого члена, он не сможет скомпилироваться так, как вы ожидали.





Вызов функции является постоянным выражением, поскольку ни один из пунктов [expr.cont]/5 не лишает ее права быть постоянным выражением. В частности, вы не пытаетесь выполнить какое-либо проблемное преобразование lvalue в rvalue в значении пункта 5.9. Единственное преобразование lvalue в rvalue, которое вы выполняете, — это value, срок жизни которого начался во время вычисления постоянного выражения, и funcPtr, который явно отмечен constexpr.
С точки зрения реализации: ваша функция myFunction не имеет доступа ни к одному элементу данных obj. Таким образом, компилятор может определить свой результат во время компиляции по заданному аргументу, и ему вообще не нужно заботиться о реальном экземпляре obj. Его состояние не влияет на результат вызова функции. Результат всегда 24.
если вы измените obj на указатель, gcc/clang больше не будет считать вызов через указатель constexpr, хотя с точки зрения реализации это то же самое (не уверен насчет стандарта). MSVC не согласился и скомпилировал его. Интересно, какой из них правильный? См.: godbolt.org/z/c1zx1cadE
@Gene Разыменование нулевого указателя (часть *obj) является явно неопределенным поведением, а неопределенное поведение лишает выражение права быть (основным) постоянным выражением для каждого элемента [expr.const]/5.8. То, что разыменование указателя само по себе вызывает UB, даже если результат не используется, было разъяснено относительно недавно в CWG 2823, хотя вызов нестатической функции-члена для нулевого указателя всегда был UB. MSVC ошибается.
@Gene Кроме того, в этом случае необходимо знать фактическое значение указателя, т. е. удаление constexpr из указателя не сработает, поскольку *obj требует преобразования lvalue в rvalue на obj. (Встроенный * можно применять только к значениям prvalues.)
@Gene Конечно, с точки зрения реализации это в любом случае не было бы проблемой, но здесь стандарт принял явное решение считать конструкцию неопределенной (либо потому, что семантически это невозможно вызывать функции-члены для несуществующего объекта или потому, что это обеспечивает большую оптимизацию нестатических функций-членов).
да, похоже, что на самом деле виноват nullptr, а не вызов через указатель. Исправление того, что каждый компилятор работает: godbolt.org/z/E74bYvonh
Вы можете сократить свою программу. См. уменьшенный пример