MSVC и constexpr для параметра функции? - PullRequest
0 голосов
/ 15 сентября 2018

Этот код прекрасно компилируется с помощью clang и gcc.

template<size_t n>
struct N {
    static constexpr size_t v = n;
};

template<size_t n>
constexpr bool operator<(N<n>, size_t n2) {
    return n < n2;
}

template<typename N>
constexpr void foo(N v) {
    static_assert(v < 5);
}

int main()
{
    foo(N<3>{});
    return 0;
}

Однако, если я использую MSVC, я получаю ошибку, что v < 5 не является константным выражением. Я могу понять, почему MSVC так думает, но я думаю, что это неправильно, и clang / gcc правы. Это ошибка от MSVC?

Ответы [ 3 ]

0 голосов
/ 15 сентября 2018

Если вы заявили:

template<size_t n>
struct N {
  int i;
  static constexpr size_t v = n;
 };

Демо здесь .

MSVC, Clang и GCC отклонят ваш код. Причина в том, что v копируется в первый параметр operator<. Такая копия является оценкой v, а v не является константным выражением.

В вашем случае N - это пустой класс. Я не думаю, что стандарт указывает, должен ли конструктор копирования такого класса обращаться к памяти объекта 1 ( основной язык, проблема 1701 ). Таким образом, компиляторы демонстрируют поведение, которое зависит от того, осуществляется ли доступ к памяти объекта пустого класса.

Clang и GCC, не обращаются к памяти объекта пустых классов, когда передано имеет параметры, но MSVC делает: посмотрите эту ссылку на проводник компилятора

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


1 Доступ к представлению объекта в памяти для копирования битов заполнения будет включать reinterpret_cast (или эквивалентный), что также запрещено в константном выражении.

0 голосов
/ 16 сентября 2018

MSVC здесь неверен, давайте начнем с упрощенной версии кода:

struct N {
    static constexpr size_t v = 0;
};

constexpr 
  bool operator<(N n1, size_t n2) {
    return n1.v < n2;
}

  void foo(N v) {
    static_assert(v < 5, ""); // C++11 does not allow terse form
}

Сначала мы рассмотрим static_assert, нарушили ли мы какие-либо правила для константных выражений?Если мы посмотрим на [expr.const] p2.2 :

вызов функции, отличной от конструктора constexpr для литерального класса или функции constexpr [Примечание: перегрузкаразрешение (13.3) применяется как обычно - конец примечания];

Все хорошо, operator< - функция constexpr, а конструктор копирования для N - конструктор constexpr для литерального класса.

Перейдем к operator< и рассмотрим сравнение n1.v < n2, и если мы посмотрим на [expr.const] p2.9 :

lvalue to-Преобразование в значение (4.1), если только оно не применяется к
- glvalue целочисленного типа или типа перечисления, которое относится к энергонезависимому объекту const с предшествующей инициализацией, инициализированным с помощью константного выражения, или
- glvalue литералатип, который ссылается на энергонезависимый объект, определенный с помощью constexpr, или ссылающийся на подобъект такого объекта, или
- glvalue литерального типа, который ссылается на энергонезависимый временный объект, время жизни которого не закончилосьИнициализируется с помощью константного выражения

Здесь мы тоже хороши.В исходном примере мы ссылаемся на шаблонный типовой аргумент, который можно использовать в константном выражении, поэтому те же рассуждения применимы и к этому случаю.Оба операнда < являются пригодными для использования константными выражениями.

Мы также можем видеть, что MSVC по-прежнему рассматривает упрощенный случай как неправильно сформированный , даже если clang и gcc принимают его.

0 голосов
/ 15 сентября 2018

Да, MSVC здесь не так.

Может показаться нелогичным, что код правильно сформирован, потому что как v, который не является константным выражением, может использоваться в константевыражение?

Так почему это разрешено?Ну, во-первых, учтите, что неформально выражение не является константным выражением, если оно вычисляется в glvalue, который сам не является константным выражением или переменной, которая начала свою жизнь за пределами включающего выражения ( [expr.const] p2.7 ).

Во-вторых, operator< - это constexpr.

Теперь получается, что v < 5 является допустимым константным выражением.Чтобы понять это, давайте пройдем оценку выражения.

У нас есть:

  1. v < 5, звонит ваш constexpr operator<
  2. Двапараметры копируются (оба они являются литералами, и ни один из них не оценивается как объект non-constexpr)
  3. n2 начал свою жизнь в рамках оценки v < 5 и является литералом
  4. nявляется параметром шаблона нетипичного типа, поэтому его можно использовать в константном выражении
  5. Наконец, n < n2 вызывает встроенный оператор.

Все они не нарушают ни одного из пунктовв [expr.const] p2 , поэтому результирующее выражение фактически является константным выражением, которое используется как аргумент для static_assert.

Эти типы выражений известны как преобразованные константные выражения .

Вот упрощенный пример:

struct Foo {
  constexpr operator bool() { return true; }
};

int main() {
  Foo f;
  static_assert(f);
}
...