constexpr перегрузка

Связано: Функция, возвращающая constexpr, не компилируется

Мне кажется, что constexpr ограничена в полезности в C ++ 11 из-за невозможности определить две функции, которые в противном случае имели бы одинаковую сигнатуру, но одна из которых была constexpr, а другая не constexpr. Другими словами, было бы очень полезно, если бы у меня был, например, конструктор constexpr std :: string, который принимает только аргументы constexpr, и конструктор std :: string, не являющийся constexpr, для аргументов, не являющихся constexpr. Другим примером может быть теоретически сложная функция, которую можно было бы сделать более эффективной, используя состояние. Вы не можете легко сделать это с помощью функции constexpr, поэтому у вас есть два варианта: иметь функцию constexpr, которая очень медленная, если вы передаете аргументы, не являющиеся constexpr, или полностью отказаться от constexpr (или написать две отдельные функции, но вы можете не знать, какую версию вызывать).

Поэтому мой вопрос таков:

Возможно ли, чтобы реализация C ++ 11, соответствующая стандарту, разрешила перегрузку функций на основе аргументов constexpr, или это потребует обновления стандарта? Если это не разрешено, было ли это сделано намеренно?


@NicolBolas: Скажем, у меня есть функция, которая отображает enum на std::string. Самый простой способ сделать это, если предположить, что мой enum идет от 0 до n - 1, - это создать массив размером n, заполненный результатом.

Я мог бы создать static constexpr char const * [] и построить std::string при возврате (оплачивая стоимость создания объекта std::string каждый раз, когда я вызываю функцию), или я могу создать static std::string const [] и вернуть значение, которое я ищу, оплатив стоимость всех std::string конструкторы при первом вызове функции. Похоже, что лучшим решением было бы создать std::string в памяти во время компиляции (аналогично тому, что делается сейчас с char const *), но единственный способ сделать это - предупредить конструктор о том, что у него есть constexpr аргументов.

Для примера, отличного от конструктора std::string, я думаю, довольно просто найти пример, в котором, если вы можете игнорировать требования constexpr (и, таким образом, создать функцию, отличную от constexpr), вы могли бы создать более эффективную функцию. Рассмотрим эту тему: constexpr question , почему эти две разные программы запускаются с g ++ за разное время?

Если я вызываю fib с аргументом constexpr, я не могу победить лучше, чем компилятор, полностью оптимизирующий вызов функции. Но если я вызываю fib с аргументом, отличным от constexpr, я могу захотеть, чтобы он вызывал мою собственную версию, которая реализует такие вещи, как мемоизация (которая требует состояния), поэтому я получаю время выполнения, подобное тому, которое было бы моим временем компиляции, если бы я пропустил constexpr аргумент.


person David Stone    schedule 20.01.2012    source источник
comment
Вы уверены, что вам это действительно нужно? Совершенно нормально вызывать constexpr функции с непостоянными аргументами.   -  person Kerrek SB    schedule 20.01.2012
comment
Есть ли у вас что-то большее, чем теоретические возможности, чтобы этого захотеть? std::string не может иметь конструктора constexpr (за исключением конструктора, который не принимает параметры), потому что он должен выделить память для строки. Даже при оптимизации с использованием небольших строк существует возможность выделения. У вас есть пример теоретически сложной функции, которую можно было бы сделать более эффективной с помощью состояния?   -  person Nicol Bolas    schedule 20.01.2012
comment
@NicolBolas: Я не верю, что это правда, посмотрите мой ответ.   -  person Ben Voigt    schedule 20.01.2012
comment
Цитата из кажется относящейся к вашему вопросу. We don’t propose to make constexpr applicable to function arguments because it would be meaningless for non-inline functions (the argument would be a constant, but the function wouldn’t know which) and because it would lead to complications of the overloading rules (can I overload on constexpr-ness? — no).   -  person Jesse Good    schedule 20.01.2012
comment
Я обновил свой вопрос, чтобы ответить Николаю Боласу. Это также должно дать ответ на озабоченность, высказанную Керреком С.Б.   -  person David Stone    schedule 21.01.2012
comment
Еще одна связанная с этим проблема, которую я недавно рассмотрел, заключается в следующем: было бы неплохо объединить assert со static_assert. Другими словами, если компилятор может определить, что утверждение не сработает (например, из-за встраивания), я хотел бы, чтобы он просто сообщал мне об этом во время компиляции, а не ждал, пока я не столкнусь с ним во время выполнения. Однако, если он не может определить его во время компиляции, мне нужно просто обычное утверждение. Кажется, самый простой способ реализовать это - что-то вроде if (is_constexpr (value)) static_assert (condition); else assert (condition);   -  person David Stone    schedule 03.04.2012
comment
if (is_constexpr (value)) static_assert (condition); else assert (condition); Устранение мертвого кода таким образом не работает.   -  person Tomilov Anatoliy    schedule 22.05.2013
comment
@ Дукалес: да, я понимаю, поэтому я сказал, что хочу, а не люблю. Большая часть кода была бы намного проще (и такой код возможен), если бы у нас был static if или как вы хотите это назвать. Конечно, даже если бы у нас было static if, я не мог бы использовать static_assert, потому что, хотя я проверил, что condition является constexpr, static_assert не знает, что я это сделал, и он все равно не компилируется.   -  person David Stone    schedule 26.05.2013
comment
Я бы тоже этого хотел. Другой пример, в котором это может быть полезно, - это количество бит / совокупность битового поля. Многие процессоры включают для этого специальные инструкции, поэтому, если функция constexpr вызывается с аргументом, отличным от constexpr, я хотел бы использовать инструкцию процессора. Но инструкции процессора недоступны во время компиляции, поэтому мне нужно использовать другой алогритм во время компиляции.   -  person pyrachi    schedule 23.07.2013
comment
Вот частичное решение проблемы: stackoverflow.com/a/39922472/6846474   -  person    schedule 07.10.2016
comment
Есть ли какое-нибудь новое решение по этой теме после C ++ 20?   -  person Adam    schedule 13.06.2020
comment
@Adam: Нет. Я работаю над статьей, ориентированной на C ++ 23: github.com/davidstone/isocpp/blob/master/   -  person David Stone    schedule 14.06.2020
comment
Хорошо, спасибо за обновление!   -  person Adam    schedule 14.06.2020
comment
Добавлен C ++ 20 std::is_constant_evaluated() см. Ответ на вопрос: Возможно ли is_constexpr в C ++ 11?   -  person Amir Kirsh    schedule 31.05.2021


Ответы (8)


Я согласен, что этой функции нет - она ​​мне тоже нужна. Пример:

double pow(double x, int n) {
    // calculate x to the power of n
    return ...
}

static inline double pow (double x, constexpr int n) {
    // a faster implementation is possible when n is a compile time constant
    return ...
}

double myfunction (double a, int b) {
    double x, y;
    x = pow(a, b);  // call version 1 unless b becomes a compile time constant by inlining
    y = pow(a, 5),  // call version 2
    return x + y;
}

Теперь мне нужно сделать это с помощью шаблонов:

template <int n>
static inline double pow (double x) {
    // fast implementation of x ^ n, with n a compile time constant
    return ...
}

Это нормально, но я упускаю возможность перегрузки. Если я делаю библиотечную функцию для использования другими, тогда неудобно, что пользователь должен использовать разные вызовы функций в зависимости от того, является ли n константой времени компиляции или нет, и может быть трудно предсказать, сократил ли компилятор n до постоянная времени компиляции или нет.

person A Fog    schedule 01.04.2012
comment
Вот еще один пример: Какой параметр функции эквивалентен constexpr? И попробуйте то же самое с шаблонами (что за беспорядок): Параметризация и «частичная специализация шаблона функции не допускается». - person jww; 04.09.2016

Изменить: трюк, описанный ниже, больше не работает!

Обнаружение constexpr не может быть выполнено с использованием перегрузок (как и другие уже ответили), но перегрузки - лишь один из способов сделать это.

Типичная проблема заключается в том, что мы не можем использовать что-то, что может улучшить производительность во время выполнения (например, для вызова функций, отличных от constexpr, или для кеширования результатов) в функции constexpr. Таким образом, мы можем получить два разных алгоритма: один менее эффективен, но доступен для записи как constexpr, а другой оптимизирован для быстрой работы, но не constexpr. Затем мы хотим, чтобы компилятор не выбирал алгоритм constexpr для значений времени выполнения и наоборот.

Это может быть достигнуто путем обнаружения constexpr и выбора на его основе «вручную», а затем сокращения интерфейса с помощью макросов препроцессора.

Сначала у нас есть две функции. В общем, функции должны достигать одного и того же результата с разными алгоритмами. Я выбираю два алгоритма, которые никогда не дают одинаковых ответов, просто чтобы проверить и проиллюстрировать идею:

#include <iostream>     // handy for test I/O
#include <type_traits>  // handy for dealing with types

// run-time "foo" is always ultimate answer
int foo_runtime(int)
{
    return 42;
}

// compile-time "foo" is factorial
constexpr int foo_compiletime(int num)
{
      return num > 1 ? foo_compiletime(num - 1) * num : 1;
}

Затем нам нужен способ определить, что аргумент является выражением константы времени компиляции. Если мы не хотим использовать специфичные для компилятора способы, такие как __builtin_constant_p, то есть способы обнаружить это и в стандартном C ++. Я почти уверен, что следующий трюк придумал Йоханнес Шауб, но я не могу найти ссылку. Очень красивый и понятный трюк.

template<typename T> 
constexpr typename std::remove_reference<T>::type makeprval(T && t) 
{
    return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

Оператор noexcept необходим для работы во время компиляции, поэтому основанное на нем ветвление будет оптимизировано большинством компиляторов. Итак, теперь мы можем написать макрос "foo", который выбирает алгоритм на основе constexprness аргумента и проверяет его:

#define foo(X) (isprvalconstexpr(X)?foo_compiletime(X):foo_runtime(X))

int main(int argc, char *argv[])
{
    int a = 1;
    const int b = 2;
    constexpr int c = 3;
    const int d = argc;

    std::cout << foo(a) << std::endl;
    std::cout << foo(b) << std::endl;
    std::cout << foo(c) << std::endl;
    std::cout << foo(d) << std::endl;
}

Ожидаемый результат:

42
2
6
42

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

person Öö Tiib    schedule 31.01.2016
comment
Кажется, это разваливается, когда вы хотите иметь возможность использовать сам результат как constexpr при применении к константе. constexpr int e = foo (c); // терпит неудачу - person Edward KMETT; 11.09.2016
comment
@EdwardKMETT Это сработает, если вы можете написать foo_runtime() как constexpr. В противном случае, да, когда нам нужно возвращать постоянное выражение, мы не можем использовать функции, которые его не возвращают. IOW constexpr int fooB = foo_compiletime(b); В C ++ нет перегрузки на основе возвращаемого типа. - person Öö Tiib; 12.09.2016
comment
@AmirKirsh Странно, что удалили еще и для режима -std = C ++ 14. Это пахнет заговором с целью сделать constexpr не обнаруживаемым каким-либо образом. - person Öö Tiib; 09.03.2020
comment
constexpr по-прежнему обнаруживается для статического хранилища, см. stackoverflow.com/a/60714976/2085626 - person Amir Kirsh; 17.03.2020
comment
Это не помогает, поскольку идея состоит не в том, чтобы назначать статические переменные, а в том, чтобы выбирать между эффективной средой выполнения, не являющейся constexpr (встроенный компилятор, встроенный ассемблер, тригономерность), и потенциально наивными, но вызовами constexpr в зависимости от того, находятся ли они в контексте constexpr или non-constexpr. - person Öö Tiib; 27.03.2020

Он должен быть перегружен на основе результата constexpr или нет, а не аргументов.

const std::string может хранить указатель на литерал, зная, что он никогда не будет записан (потребуется использование const_cast для удаления const из std::string, а это уже неопределенное поведение). Было бы просто необходимо сохранить логический флаг, чтобы запретить освобождение буфера во время разрушения.

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


Согласно стандарту (раздел 7.1.6.1 [dcl.type.cv]) изменение любого объекта, который был создан const, является неопределенным поведением:

За исключением того, что любой член класса, объявленный изменяемым (7.1.1), может быть изменен, любая попытка изменить константный объект во время его жизни (3.8) приводит к неопределенному поведению.

person Ben Voigt    schedule 20.01.2012
comment
const не тип; это модификатор для типа. Ваш код не может знать, что он находится в истинном const std::string, а не только в std::string, к которому прямо сейчас const обращается. Без этого знания, без возможности отличить эту оптимизацию просто невозможно. Вот почему кто-то написал старый boost::const_string класс; потому что нет возможности реализовать его без специализированного кода, который может определить, что он всегда постоянный. - person Nicol Bolas; 20.01.2012
comment
@NicolBolas: Верно, но я не думаю, что Бен говорит, что его предложение возможно. Было бы интересно разрешить применение модификатора const к конструкторам. - person Potatoswatter; 20.01.2012
comment
@NicolBolas: У вас все наоборот. Код никогда не должен использовать const_cast для изменения const std::string (очевидно, с помощью указателя или ссылки), если только он не знает извне, что объект не был создан const. Изменение созданного const объекта уже является неопределенным поведением, добавление этой оптимизации ничего не сломает. Я процитировал именно этот раздел стандарта. - person Ben Voigt; 20.01.2012
comment
@BenVoigt: Это не меняет моей точки зрения. У конструктора нет способа узнать, что создаваемый им объект является объектом const. Без этого невозможно сохранить специальные данные, которые const функции-члены могут отключать, чтобы знать, что объект действительно является const объектом, а не просто объектом, к которому в настоящее время осуществляется доступ как const (передача значения функции, которая принимает const&). Конструктор constexpr не должен создавать const объектов. Вы говорите о совершенно новом виде конструкции, которой нет в C ++ 11. - person Nicol Bolas; 20.01.2012
comment
@NicolBolas: Я подумал, что правильно осветил этот вопрос в первом предложении своего ответа. Но тебе все еще чего-то не хватает. Поскольку const функции-члены не могут определить, является ли объект const, они должны предполагать, что это так, и не вносить изменений. Единственная проблема - это деструктор. - person Ben Voigt; 20.01.2012
comment
@BenVoigt: Речь идет не о const функциях-членах. Дело в конструкторе. Конструктор должен поместить объект в особое состояние хранения буквального указателя на строку. Для этого конструктор должен быть абсолютно уверен в трех вещах: 1) конструктор вызывается во время компиляции через constexpr. 2) аргумент конструктора - строковый литерал. 3) конструируемый объект является const объектом. Тот факт, что конструктор является constexpr конструктором, не означает, что он создает объект const. В C ++ 11 невозможно обнаружить какие-либо из них. - person Nicol Bolas; 21.01.2012
comment
@Nicol: (1) не требуется. (2) возможно с использованием обобщенных литералов (которые в любом случае необходимы для получения длины). И (3) была вся суть моего ответа. - person Ben Voigt; 21.01.2012
comment
@BenVoigt: определяемые пользователем литералы не могут вызывать какой-либо конструктор, который иначе не мог бы вызвать ваш код. Так что, если они могут вызвать специальный конструктор std::string, то можете и вы. И ваш ответ не объясняет, как std::string конструктор узнает, что он создает объект const. - person Nicol Bolas; 21.01.2012
comment
@NicolBolas: Ага, в том-то и дело. Это потребует перегрузки в зависимости от того, является ли создаваемый объект const. C ++ этого не делает. Это мой ответ :) - person Ben Voigt; 21.01.2012
comment
Проблема с использованием определяемых пользователем литералов для принудительного использования строкового литерала заключается в том, что вы ничего не навязываете. Да, "foo"_x требуется литерал, а operator""_x(a) - нет. - person R. Martinho Fernandes; 23.04.2012
comment
@ R.MartinhoFernandes: Возможно, но я не принимаю во внимание тот факт, что пользователь может запускать UB, пытаясь достаточно усердно решить проблему. В конце концов, это же C ++. Цель не в том, чтобы помешать пользователю взломать свой код, а в том, чтобы построить яму успеха. - person Ben Voigt; 23.04.2012
comment
@BenVoigt а, конечно, я согласен с этим: если вы идете по пути operator""_x, вы делаете это на свой страх и риск. Просто указываю, что UDL ничего не навязывают, кроме как по соглашению. - person R. Martinho Fernandes; 23.04.2012

Хотя в C ++ 11 нет такой вещи, как «перегрузка constexpr», вы все равно можете использовать GCC / Clang __builtin_constant_p intrinsic. Обратите внимание, что эта оптимизация не очень полезна для double pow(double), потому что и GCC, и Clang уже могут оптимизировать pow для постоянных целочисленных показателей, но если вы напишете мультиточную или векторную библиотеку, эта оптимизация должна работать.

Посмотрите этот пример:

#define my_pow(a, b) (__builtin_constant_p(b) ? optimized_pow(a, b) : generic_pow(a, b))

double generic_pow(double a, double b);

__attribute__((always_inline)) inline double optimized_pow(double a, double b) {
    if (b == 0.0) return 1.0;
    if (b == 1.0) return a;
    if (b == 2.0) return a * a;
    if (b == 3.0) return a * a * a;
    if (b == 4.0) return a * a * a * a;

    return generic_pow(a, b);
}

double test(double a, double b) {
    double x = 2.0 + 2.0;
    return my_pow(a, x) + my_pow(a, b);
}

В этом примере my_pow(a, x) будет расширен до a*a*a*a (благодаря устранению мертвого кода), а my_pow(a, b) будет расширен до прямого вызова generic_pow без каких-либо предварительных проверок.

person Lockal    schedule 27.02.2014
comment
Жалко, что __builtin_constant_p не работает с аргументами функции constexpr. - person Marc Mutz - mmutz; 12.02.2015

Проблема, как уже говорилось, кажется неправильной.


std::string, по построению, владеет памятью. Если вам нужна простая ссылка на существующий буфер, вы можете использовать что-то вроде llvm::StringRef:

class StringRef {
public:
  constexpr StringRef(char const* d, size_t s): data(d), size(s) {}

private:
  char const* data;
  size_t size;
};

Конечно, есть облом, что strlen и все остальные функции C не constexpr. Это похоже на дефект Стандарта (подумайте обо всех математических функциях ...).


Что касается состояния, вы можете (немного), если вы понимаете, как его хранить. Помните, что циклы эквивалентны рекурсиям? Точно так же вы можете «сохранить» состояние, передав его в качестве аргумента вспомогательной функции.

// potentially unsafe (non-limited)
constexpr int length(char const* c) {
  return *c == '\0' ? 0 : 1 + length(c+1);
}

// OR a safer version
constexpr int length_helper(char const* c, unsigned limit) {
  return *c == '\0' or limit <= 0 ? 0 : 1 + length_helper(c+1, limit-1);
}

constexpr int length256(char const* c) { return length_helper(c, 256); }

Конечно, эта форма этого состояния несколько ограничена (нельзя использовать сложные конструкции), и это ограничение constexpr. Но это уже огромный шаг вперед. Идти дальше означало бы углубиться в чистоту (что вряд ли возможно в C ++).

person Matthieu M.    schedule 20.01.2012

Возможно ли, чтобы реализация C ++ 11, соответствующая стандарту, разрешила перегрузку функций на основе аргументов constexpr, или это потребует обновления стандарта? Если это не разрешено, было ли это сделано намеренно?

Если в стандарте не сказано, что вы можете что-то делать, то позволять кому-то это делать было бы нестандартным поведением. И поэтому компилятор, который позволил бы это реализовать, будет реализовывать расширение языка.

В конце концов, это не обязательно плохо. Но это было бы несовместимо с C ++ 11.

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

person Nicol Bolas    schedule 20.01.2012
comment
SFINAE можно использовать для реализации перегрузки с правилами, отличными от правил по умолчанию. Использование аргумента функции в качестве параметра шаблона в подписи может быть законным способом отключить функцию для аргументов, не являющихся constexpr. А может и нет. Я не буду искать его, потому что, как объясняет Бен, результат в любом случае будет бесполезен. - person Potatoswatter; 20.01.2012

Другой вариант обнаружения компиляции во время компиляции с помощью SFINAE: http://coliru.stacked-crooked.com/a/f3a2c11bcccdb5bf

template<typename T>
auto f(const T&)
{
  return 1;
}

constexpr auto f(int)
{
  return 2;
}



////////////////////////////////////////////////////////////////////////
template<typename T, int=f(T{})>
constexpr bool is_f_constexpr_for(int) {return true;}

template<typename...>
constexpr bool is_f_constexpr_for(...) {return false;}



template<typename T>
auto g(const T& t)
{
  if constexpr (is_f_constexpr_for<T>(0))
  {

  }
  else
  {

  }
}
person NN_    schedule 23.08.2018

Можно определить, является ли данная переменная статического хранилища постоянным выражением, используя подход предложено Ричардом Смитом на основе сужающих правил преобразования.

Мы можем присвоить unsigned int consexpr неотрицательный int без сужения:

unsigned int u {std::max(0, -3)}; // compiles, max is constexpr

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

int a = 3;
unsigned int u {std::max(0, a)}; // compilation error, narrowing int to unsigned int

Чтобы определить, является ли данное int reference константным выражением, мы можем проверить, может ли оно быть присвоено unsigned int без сужения с положительным или отрицательным значением. Это должно быть возможно для любого int, значение которого известно во время компиляции, т.е. может рассматриваться как постоянное выражение.

template<const int& p> std::true_type
    is_constexpr_impl(decltype((unsigned int){std::max(-p, p)}));
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr =
    decltype(is_constexpr_impl<p>(0));

Теперь у нас могут быть разные реализации для времени выполнения и времени компиляции с помощью макроса:

int foo_runtime(int num) {
    return num;
}

constexpr int foo_compiletime(int num) {
      return num + 1;
}

#define foo(X) (is_constexpr<X>()?foo_compiletime(X):foo_runtime(X))

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

int main() {
    static int a = 3;
    static const int b = 42; // considered constexpr
    static const int c = foo_runtime(42); // not constexpr
    static constexpr int d = 4;
    static constexpr int e = -2;
    static int f = 0;
    static const int g = 0; // considered constexpr

    std::cout << foo(a) << std::endl;
    std::cout << foo(b) << std::endl;
    std::cout << foo(c) << std::endl;
    std::cout << foo(d) << std::endl;
    std::cout << foo(e) << std::endl;
    std::cout << foo(f) << std::endl;
    std::cout << foo(g) << std::endl;
}

Вышеизложенный вариант хорош, но не очень полезен, поскольку он ограничен переменными статическим хранилищем. Но есть перегрузка на основе constexpr.


Другой подход для достижения того же, независимо от сужения преобразования, может быть:

template<const int& p> std::true_type
    is_constexpr_impl(std::array<int, std::max(p, -p)>);
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr = 
    decltype(is_constexpr_impl<p>(0));

Использование std::array выше заменяет использование простого c-массива, , который не подходит для gcc с этим подходом.


Или другой - опять же, не полагаясь на правила сужения - , который также отлично работает:

template<const int& p, typename T = void>
struct is_constexpr: std::false_type {};

template<const int& p>
struct is_constexpr<p, std::void_t<int[std::max(p,-p)+1]>>: std::true_type {};

Обратите внимание, что если мы попытаемся добиться того же с помощью более простой подход:

template<typename T>
struct is_constexpr: std::false_type {};

template<typename T>
struct is_constexpr<const T>: std::true_type {};

#define foo(X) (is_constexpr<decltype(X)>()?foo_compiletime(X):foo_runtime(X))

Мы не достигли нашей цели по этой строке:

static const int c = foo_runtime(42); // const but not constexpr
person Amir Kirsh    schedule 17.03.2020
comment
Вы можете объяснить, какая версия здесь в нижней строке? т.е. что рекомендуется к использованию? - person einpoklum; 14.04.2020
comment
@einpoklum, каковы ваши требования к использованию? - person Amir Kirsh; 15.04.2020
comment
На данный момент ничего, просто любопытно. - person einpoklum; 15.04.2020
comment
@einpoklum идея заключалась в том, чтобы представить, что могут быть разные подходы к проверке is_constexpr, то есть с использованием: (1) правил сужения, (2) параметра, не являющегося типом, (3) размера массива. Однако все эти параметры работают только для статических переменных хранилища, поскольку все они основаны на передаче проверяемой переменной в качестве параметра шаблона по ссылке. - person Amir Kirsh; 14.05.2020
comment
Более простой реализацией, чем std::max(x, -x), которая работает со всеми типами (и INT_MIN), будет (void(expr), 0). Он всегда производит значение 0, но является константным выражением только в том случае, если первый аргумент оператора запятой является константным выражением. - person David Stone; 22.05.2021