перегрузка constexpr - PullRequest
       25

перегрузка constexpr

43 голосов
/ 20 января 2012

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

Мне кажется, что constexpr ограничен в полезности в C ++ 11 из-за невозможности определить две функции, которые в противном случае имели бы одинаковыеподпись, но один должен быть constexpr, а другой не constexpr.Другими словами, было бы очень полезно, если бы у меня мог быть, например, конструктор constexpr std :: string, который принимает только аргументы constexpr, и конструктор non-constexpr std :: string для аргументов не 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 arguments.

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

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

Ответы [ 7 ]

34 голосов
/ 01 апреля 2012

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

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 до постоянная времени компиляции или нет.

8 голосов
/ 31 января 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

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

7 голосов
/ 20 января 2012

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

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

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


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

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

6 голосов
/ 27 февраля 2014

Хотя в C ++ 11 нет такой вещи, как «перегрузка constexpr», вы все равно можете использовать встроенную функцию GCC / Clang __builtin_constant_p.Обратите внимание, что эта оптимизация не очень полезна для 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 без каких-либо предварительныхчеки.

4 голосов
/ 20 января 2012

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


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

2 голосов
/ 20 января 2012

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

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

Это не обязательно плохо, в конце концов. Но это не будет соответствовать C ++ 11.

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

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

Еще одна опция для обнаружения компиляции во время компиляции с использованием 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
  {

  }
}
...