Подтвердить что ты не робот

Перегрузка constexpr

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

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

Поэтому мой вопрос заключается в следующем:

Возможно ли, чтобы стандартная реализация С++ 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, почему эти две разные программы выполняются за такое разное время с g++?

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

4b9b3361

Ответ 1

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

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

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


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

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

Ответ 2

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

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

Ответ 3

Обнаружение и использование 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;
}

Вот трюк обнаружения constexpr. Я почти уверен, что он изобрел Йоханнес Шауб, но я не могу найти цитату. Очень приятно и ясный трюк.

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

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

Итак, остается только написать "foo", который выбирает алгоритм, основанный на по типу аргумента и протестировать его:

#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

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

Ответ 4

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


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. Но это уже огромный скачок вперед. Дальнейшее продвижение означало бы глубже проникнуть в чистоту (что вряд ли возможно на С++).

Ответ 5

Хотя в С++ 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 без предварительных проверок.

Ответ 6

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

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

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

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