Предполагается, что std :: less позволяет сравнивать несвязанные указатели во время компиляции? - PullRequest
39 голосов
/ 21 июня 2020

Рассмотрим этот код:

#include <functional>
#include <typeinfo>

template <typename T>
inline constexpr const void *foo = &typeid(T);

int main()
{
    constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);
} 

Запуск на g cc .gotbolt.org

Если я использую < вместо std::less здесь код не компилируется. Это неудивительно, потому что результат сравнения реляционных указателей не указан , если указатели указывают на несвязанные объекты, и, очевидно, такое сравнение не может быть выполнено во время компиляции.

<source>:9:20: error: constexpr variable 'a' must be initialized by a constant expression
    constexpr bool a = foo<int> < foo<float>;
                   ^   ~~~~~~~~~~~~~~~~~~~~~
<source>:9:33: note: comparison has unspecified value
    constexpr bool a = foo<int> < foo<float>;
                                ^

Код по-прежнему не компилируется, даже если я использую std::less. Ошибка компилятора такая же. std::less кажется реализованным как < по крайней мере в libstdc ++ и libc ++; Я получаю те же результаты на G CC, Clang и MSV C.

Однако на странице cppreference about std::less утверждается, что:

  1. Его operator() равно constexpr.

  2. Он волшебным образом реализует строгий общий порядок указателей, т.е. может использоваться для сравнения несвязанных указателей с разумными результатами.

Итак, это ошибка всех этих компиляторов, или мне не хватает каких-то деталей о std::less, из-за которых приведенный выше код имеет неправильный формат?

Ответы [ 3 ]

28 голосов
/ 21 июня 2020

Я не думаю, что есть четкий ответ на вопрос, который вы задаете. Это особый c случай LWG 2833 : пометка библиотечной функции constexpr не объясняет обстоятельства, при которых вызов функции приведет к постоянному выражению.

До этой проблемы разрешено, я думаю, вы просто не можете полагаться на std::less возможность сравнивать несвязанные указатели во время компиляции.

5 голосов
/ 21 июня 2020

Чтобы функция была действительной constexpr, она должна иметь параметры, для которых результат равен constexpr, не обязательно все параметры.

Например,

constexpr int foo(bool b) { if (!b) throw 42; return 42; }

действительно, f(true) можно использовать в constexpr (даже если f(false) не может).

constexpr int a[2]{};
constexpr bool b = std::less<const void*>{}(&a[0], &a[1]);

действительно и достаточно, чтобы less::operator() было constexpr.

Я не Думаю, в стандарте указано, какие диапазоны / значения верны для constexpr.

Значит, все компиляторы верны.

0 голосов
/ 22 июня 2020

В вашем вопросе вы объявляете переменную, например constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);, переменная constexpr требуется для удовлетворения следующего правила:

Спецификатор constexpr, используемый в объявлении объекта, объявляет объект как const. Такой объект должен иметь буквальный тип и быть инициализирован. В любом объявлении переменной constexpr полное выражение инициализации должно быть постоянным выражением . (Обратите внимание на выделенную часть)

постоянное выражение должно быть основным постоянным выражением, следовательно, оно должно удовлетворять основным правилам выражения констант:

a relational или оператор равенства, где результатом является не указано ;

В вашем коде &typeid(int) и &typeid(float) не указаны из-за:

Сравнение неравных указателей с объектами определяется следующим образом:

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

&typeid(int) и &typeid(float) соответствуют третьему пункту. И

Если два операнда p и q сравниваются равными, p <= q и p> = q оба дают true, а pq оба дают false. В противном случае, если указатель p сравнивается больше, чем указатель q, p> = q, p> q, q <= p и q = p и q> p все дают false. В противном случае результат каждого из операторов не указан. .

Итак, результат сравнения &typeid(int) с &typeid(float) не указан, следовательно, это не постоянное выражение.

...