Когда следует использовать возможность constexpr в C++11?

Мне кажется, что наличие "функции, которая всегда возвращает 5" нарушает или ослабляет значение "вызова функции". Должна быть причина, или необходимость в этой возможности, иначе ее не будет в C++11. Почему это там?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Мне кажется, что если бы я написал функцию, возвращающую буквальное значение, и подошел к пересмотру кода, кто-то сказал бы мне, я должен был бы объявить постоянное значение вместо написания return 5.

15 ответов

Решение

Предположим, он делает что-то более сложное.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

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

Это в основном обеспечивает хорошую поддержку для ремонтопригодности, поскольку становится более очевидным, что вы делаете. принимать max( a, b ) например:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

Это довольно простой выбор, но это означает, что если вы позвоните max с постоянными значениями он явно рассчитывается во время компиляции, а не во время выполнения.

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

const float oneeighty = DegreesToRadians( 180.0f );

Много полезной информации здесь:

http://en.cppreference.com/w/cpp/language/constexpr

Вступление

constexpr не был представлен как способ сказать реализации, что что-то может быть оценено в контексте, который требует константного выражения; Соответствующие реализации смогли доказать это до C++11.

То, что реализация не может доказать, является намерением определенного фрагмента кода:

  • Что разработчик хочет выразить этой сущностью?
  • Должны ли мы слепо допустить использование кода в константном выражении только потому, что это работает?

Без чего мир был бы constexpr?

Допустим, вы разрабатываете библиотеку и понимаете, что хотите вычислить сумму каждого целого числа в интервале (0,N],

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

Отсутствие намерений

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

Теперь приходит кто-то другой, читает вашу функцию, выполняет тот же анализ, что и компилятор; "О, эту функцию можно использовать в постоянном выражении!" и пишет следующий фрагмент кода.

T arr[f(10)]; // freakin' magic

Оптимизация

Вы, как "потрясающий" разработчик библиотеки, решаете, что f должен кэшировать результат при вызове; кто хотел бы рассчитывать один и тот же набор значений снова и снова?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

Результат

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

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


Итак, зачем нам constexpr?

Основное использование constexpr - объявить намерение.

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

Принимать std::numeric_limits<T>::max()по какой-то причине, это метод. constexpr было бы полезно здесь.

Другой пример: вы хотите объявить C-массив (или std::array), который так же велик, как другой массив. Способ сделать это на данный момент так:

int x[10];
int y[sizeof x / sizeof x[0]];

Но не лучше ли было бы написать:

int y[size_of(x)];

Благодаря constexpr, вы можете:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

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

Тем не менее, одно из применений constexpr не имеет эквивалента C++03, типизированных констант.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

Из того, что я прочитал, необходимость в constexpr проистекает из проблемы метапрограммирования. Классы черт могут иметь константы, представленные в виде функций, подумайте: numeric_limits::max(). С constexpr эти типы функций могут использоваться в метапрограммировании или в качестве границ массивов и т. Д. И т. Д.

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

Редактировать:

После изучения SO, похоже, что другие придумали несколько примеров того, что может быть возможно с constexprs.

Из выступления Страуструпа на "Going Native 2012":

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

Другое использование (еще не упомянутое) constexpr Конструкторы. Это позволяет создавать константы времени компиляции, которые не нужно инициализировать во время выполнения.

const std::complex<double> meaning_of_imagination(0, 42); 

Соедините это с пользовательскими литералами, и вы получите полную поддержку литеральных пользовательских классов.

3.14D + 42_i;

Только что начали переключать проект на C++11 и столкнулись с совершенно хорошей ситуацией для constexpr, которая убирает альтернативные методы выполнения той же операции. Ключевым моментом здесь является то, что вы можете поместить функцию в объявление размера массива только тогда, когда она объявлена ​​constexpr. Есть ряд ситуаций, когда я вижу, что это очень полезно для продвижения вперед в той области кода, в которой я участвую.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}

Раньше был шаблон с метапрограммированием:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

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

Однако обратите внимание, что:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Скомпилируйте это с g++ -O3 и вы увидите, что fact(10) действительно оценивается во время компиляции!

Компилятор с поддержкой VLA (например, компилятор C в режиме C99 или компилятор C++ с расширениями C99) может даже позволить вам сделать:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

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

Все остальные ответы великолепны, я просто хочу привести классный пример того, что вы можете сделать с constexpr, что удивительно. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h) - это HTML-анализатор и шаблонизатор времени компиляции. Это означает, что вы можете вставить HTML и получить дерево, которым можно манипулировать. Выполнение синтаксического анализа во время компиляции может дать вам дополнительную производительность.

Из примера страницы github:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

Во многих ответах здесь, кажется, все немного наоборот, и / или говорится, что тихая часть громкая, а громкая часть тихая. Одна ключевая вещь, о которой нужно знать, заключается в следующем:

      // This guarantees only that the value of "MeaningOfLife" can not be changed
// from the value calculated on this line by "complex_initialization()"
// (unless you cast away the const of course, don't do that).
// Critically here, everything happens at *runtime*.
const int MeaningOfLife = complex_initialization(1234, 5678, "hello");
      // This guarantees that "MeaningOfLife" is fully evaluated and "initialized"
// *at compile time*.  If that is not possible due to complex_initialization()
// not being evaluatable at compile time, the compiler is required to abort
// compilation of the program.
// Critically here, to put a fine point on it, everything happens at
// *compile time*, guaranteed.  There won't be a runtime call to
// complex_initialization() at all in the final program.
constexpr int MeaningOfLife = complex_initialization(1234, 5678, "hello");

Заметьте, что именно -ность левой части вынуждает гарантировать ее существование. Конечно, вы должны убедиться, что правая часть действительно может быть оценена во время компиляции, и, что важно, простое объявление функции само по себе не делает этого.

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

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

static const int x = 5;
int arr[x];

над

int arr[5];

Потому что это намного удобнее в обслуживании. Использование constexpr намного, намного быстрее, чтобы писать и читать, чем существующие методы метапрограммирования.

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

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

class MyInterface {
public:
    int GetNumber() const = 0;
};

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

Добавление constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

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

Когда использовать constexpr:

  1. всякий раз, когда есть постоянная времени компиляции.

Это полезно для чего-то вроде

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

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

Другие вопросы по тегам