Почему Сonstexpr для не константного аргумента не дает ошибки?

Вот такой код

constexpr  uint8_t dizaine(uint8_t x) {
  return  ((x) / 10);
}

void setup() {
  // put your setup code here, to run once:
uint8_t h = millis() & 0xff;
uint8_t heureDizaine = dizaine(h);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Почему он компилируется без ошибок? Ведь аргумент не является константой, известной на момент компиляции, что обязательно для constexpr?

Arduino IDE 1.8.19
плата - Arduino Mega
AVR Core v1.8.3

Компилятор оптимизнул.

Если включить ВСЕ варнинги, то это станет видно:

код (cygwin или linux)

#include <stdlib.h>
#include <stdint.h>

int millis() {
  return random();
}

constexpr  uint8_t dizaine(uint8_t x) {
  return  ((x) / 10);
}

void setup() {
  // put your setup code here, to run once:
  uint8_t h = millis() & 0xff;
  uint8_t heureDizaine = dizaine(h);
}

void loop() {
  // put your main code here, to run repeatedly:

}

int main() {


  setup();
  while(true)
    loop();
}

Вопрос снят, со второго раза нашел ответ

AI Overview
In C++,
constexpr functions can be used with non-constant (runtime) arguments, making them highly versatile for both compile-time optimization and runtime execution. While constexpr variables must be initialized with a constant expression, constexpr functions are designed to work in both contexts.
Here is a breakdown of how constexpr interacts with non-constant values:

  1. constexpr Functions with Runtime Arguments
    A constexpr function acts as a normal function at runtime if its arguments are not known at compile time.

    Behavior: The function is evaluated at runtime, similar to a regular function.
    Purpose: This allows you to write a single function that can be used for compile-time constants (e.g., template arguments) and also for variable data (e.g., user input).

2 лайка

Ответ неверный.
Как оказалось, constexpr может использоваться для runtime переменных, это не ошибка и даже не предупреждение. Читайте #4

А варнинг на вашей картинке совсем не об этом.

Век живи - век учись. Но варнинг на картинке говорит о том, что компилятор выкинул вызов к вашей constexpr функции. Между строк, намекая.

Можете проверить, посмотрев ассемблерный листинг :slight_smile:

1 лайк

Для полноты рассмотрения вопроса, я бы добавил, что в тех случаях, когда нужно однозначно гарантировать вычисления во время компиляции, следует использовать более сильный, чем constexpr спецификатор consteval. Это гарантирует вычисление на этапе компиляции или, в случае невозможности, приведёт к ошибке компиляции.

Также, если нужно обеспечить гарантированную инициализацию переменной на этапе компиляции, следует использовать constinit.

Главное отличие constinit от consteval в том, что в первом случае речь идёт о переменной (а не о константе). Она будет проинициализирована при компиляции, но потом никто не запрещает её изменять.

constinit и consteval доступны, начиная с С++20 (если склероз не изменяет).

5 лайков

Если сравнивать Си функцию с attribute((pure)) или attribute((const)) с функцией Си++, объявленной, как constexpr: одинаково ли компилятор понимает где и когда можно делать CSE (common subexpression elimination)? Например, пишет кто-то

a = func(1) + func(1) + func(1) + func(1);

Вопрос: свернет ли компилятор это в

a = 4 * func(1) // например

или будет честно вызывать функцию 4 раза подряд? Указывает ли constexpr компилятору как-то явно или не явно, что функция зависит только от аргументов?

Он может ещё и не в такое свернуть.

Например, попробуйте скомпилировать вот такой скетч для обычного Uno в IDE с опциями “из коробки”

long fact(long n) {
	return n < 1 ? 1 : n * fact(n - 1);
}

void setup() {
	PORTB = (fact(5) - fact(4)) / fact(3);
}

void loop(void){}

А потом посмотрите в дизассемблер и убедитесь, что строку №6 он свернул в

PORTB = 16;

В дизассемблере это:

 1b0:	80 e1       	ldi	r24, 0x10	; 16
 1b2:	85 b9       	out	0x05, r24	; 5

Вообще, пытаться угадать, что сделает оптимизатор – так себе занятие. У них (у оптимизаторов) свои правила и заморочки.

1 лайк

Ну это вы просто в одном файле все сделали. Если бы вы fact() определили бы как extern, то вот тут бы самое интересное началось: компилятор не знает, что там внутри fact(). Вопрос, в том, что он сделает увидев a = fact(10) + fact(10).

Не для меня. Мне это неинтересно, я же говорил