Использование аргумента функции как части константного выражения - gcc vs clang - PullRequest
0 голосов
/ 24 мая 2018

Рассмотрим следующий фрагмент кода:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
  • clang ++ (транк) компилирует код

  • g ++ (транк) не удалось скомпилировать со следующей ошибкой:

    src:7:34: error: template argument 1 is invalid
    auto f(T t) -> decltype(B<pred(t)>{})
                                    ^
    
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:25: error: invalid template-id
    auto f(T t) -> decltype(B<pred(t)>{})
                            ^
    
    src:7:36: error: class template argument deduction failed:
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    
    src:7:36: error: no matching function for call to 'B()'
    src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
    template <bool> struct B { };
                            ^
    
    src:1:24: note:   template argument deduction/substitution failed:
    src:7:36: note:   couldn't deduce template parameter '<anonymous>'
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    

    живой пример на godbolt.org


Несмотря на то, что диагностика g ++ вводит в заблуждение, я предполагаю, что проблема здесь в том, что t не является константным выражением .Изменение кода на ...

decltype(B<pred(T{})>{})

... исправляет ошибку компиляции на g ++: живой пример на godbolt.org


Какой компилятор ведет себя правильно здесь?

Ответы [ 3 ]

0 голосов
/ 08 июля 2018

t не является значением constexpr, это значение pred(t) также не является constexpr.Вы не можете использовать его в B<pred(t)>, потому что для этого нужно constexpr.

Эта версия компилируется правильно:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}

https://godbolt.org/g/ydbj1X

Другой допустимый код:

template <typename T>
auto f(T t) -> decltype(pred(t))
{
}

Это потому, что вы не оцениваете pred(t), только вы получаете информацию о типе.B<pred(t)> нужна оценка pred(t), в противном случае вы получите B<true> или B<false> для любого нормального значения, которое вы не можете сделать с этим.

std::integral_constant<int, 0>{} может работать в случае Clang, вероятно, потому чтоего значение встроено как часть типа и всегда одинаково.Если мы немного изменим код:

template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
    return {};
}

и Clang, и GCC скомпилируют его, в случае std::integral_constant, t и decltype(t){} всегда будут иметь одинаковое значение.

0 голосов
/ 25 августа 2018

GCC неверен. Нет правила, запрещающего использование параметров функции в константном выражении таким образом.

Однако вы не можете использовать значение параметра в таком контексте, и набор типов T, для которых f может быть вызван, весьма ограничен.Чтобы понять почему, нам нужно рассмотреть, какие конструкции будут оцениваться при оценке выражения pred(t):

// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});

Семантика оценки для вызова pred(t) следующая:

  1. copy-initialize pred параметр u из параметра f t
  2. вычисляет тело pred, которое тривиально создает значение bool true
  3. destroy u

Таким образом, f может вызываться только для типов T, для которых вышеуказанное включает только конструкции, действительные во время постоянной оценки (см. [expr.const] p2 для правил).Требования:

  • T должен быть литеральным типом
  • copy-инициализация u из t должна быть константным выражением и, в частности, не должнавыполнить преобразование lvalue в rvalue для любого члена t (поскольку их значения неизвестны), и не должен называть ни одного ссылочного члена t

На практике это означает, чтоf вызывается, если T является пустым типом класса с конструктором копирования по умолчанию, или если T является типом класса с конструктором копирования constexpr и не читает никаких членов своего аргумента, или (как ни странно)если T равно std::nullptr_t (хотя clang в настоящее время неправильно получает nullptr_t регистр ).

0 голосов
/ 15 июня 2018

Компилятор ожидает параметр в этом контексте, потому что он должен оценить полный (перегруженный шаблоном) тип функции.Учитывая реализацию pred, любое значение будет работать в этом месте.Здесь он связывает тип шаблона параметра f с аргументом.Компилятор g ++, похоже, делает упрощающее предположение, что функция шаблона constexpr будет каким-то образом изменена любыми параметрами, если только они не являются const, что, как вы продемонстрировали, и clang согласен, не обязательно так.

Все сводится к тому, как глубоко внутри реализации функции компилятор помечает функцию как неконстантную из-за неконстантного вклада в возвращаемое значение.

Тогда возникает вопросо том, является ли функция созданной и требует ли компилятор фактической компиляции кода по сравнению с выполнением синтаксического анализа шаблона, который, по крайней мере с g ++, представляется другим уровнем компиляции.

Затем я перешел к стандарту, и они любезнопозволить составителю компилятора сделать именно это упрощающее предположение, и создание экземпляра функции шаблона должно работать только для f<const T> или f <const T&>

функции constexpr`: каждый из ее параметров должен быть LiteralType

Так шаблон кодадолжен скомпилироваться, но потерпеть неудачу, если создан с неконстантным T.

...