Почему вызов функции constexpr с массивом-членом не является постоянным выражением?

У меня есть следующая вспомогательная функция:

template<typename T, std::size_t N>
constexpr std::size_t Length(const T(&)[N]) {
    return N;
}

Который возвращает длину статического массива. В прошлом это всегда срабатывало, но когда я это делаю:

struct Foo
{
    unsigned int temp1[3];
    void Bar()
    {
        constexpr std::size_t t = Length(temp1); // Error here
    }
};

Я получаю сообщение об ошибке при использовании MSVS 2017:

error C2131: expression did not evaluate to a constant

note: failure was caused by a read of a variable outside its lifetime

note: see usage of 'this'

Я надеялся, что кто-то может пролить свет на то, что я делаю неправильно.

Ответ 1

MSVC является правильным. Length(temp1) не является постоянным выражением. Из [expr.const] p2

Выражение e является выражением основной константы, если оценка e, следуя правилам абстрактной машины, не будет оценивать одно из следующих выражений:

  • this, за исключением функции constexpr или конструктора constexpr, который оценивается как часть e;

temp1 оценивает this неявно (потому что вы имеете в виду this->temp1), и поэтому у вас нет постоянного выражения. gcc и clang принимают его, потому что они поддерживают VLA как расширение (попробуйте -Werror=vla компиляцию с -Werror=vla или -pedantic-errors).

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

Ответ 2

Length(decltype(temp1){})

похоже работа.

К сожалению, я не могу комментировать, но решение Мехрдада неверно. Причина: это не технически неопределенное поведение, но это неопределенное поведение. Во время оценки constexpr компилятор должен улавливать неопределенное поведение. Поэтому код плохо сформирован.

Ответ 3

На ваш вопрос уже был дан ответ, но с точки зрения того, как его "исправить", быстрый и грязный способ - заменить

Length(temp1)

с

Length(*(true ? NULL : &temp1))

который, по моему мнению, является технически неопределенным поведением, но практически подходит для MSVC.

Если вам нужно решение, которое работает, несмотря на UB, вы можете изменить Length чтобы использовать указатель:

template<typename T, std::size_t N>
constexpr std::size_t Length(const T(*)[N]) {
    return N;
}

а затем вы можете использовать Length(true? NULL: &temp1).