перегрузка constexpr

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

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

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

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


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

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

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

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

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

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

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


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

За исключением того, что любой член classа, объявленный mutable (7.1.1), может быть модифицирован, любая попытка изменить объект const во время его жизни (3.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  static inline double pow (double x) { // fast implementation of x ^ n, with na compile time constant return ... } 

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

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

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

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

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

 #include  // handy for test I/O #include  // 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 constexpr typename std::remove_reference::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 

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

Хотя в C ++ 11 нет такой вещи, как «constexpr overloading», вы все равно можете использовать 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 без каких-либо предварительных проверок.

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


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 . Это похоже на дефект Стандарта (подумайте обо всех функциях математики …).


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

 // 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 ++).

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

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

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

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

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

 template auto f(const T&) { return 1; } constexpr auto f(int) { return 2; } //////////////////////////////////////////////////////////////////////// template constexpr bool is_f_constexpr_for(int) {return true;} template constexpr bool is_f_constexpr_for(...) {return false;} template auto g(const T& t) { if constexpr (is_f_constexpr_for(0)) { } else { } } 
Давайте будем гением компьютера.