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


// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

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

13 ответов:

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

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

еще один хороший пример будет

введение

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];
}

результат

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

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


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

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

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

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

другой пример: вы хотите объявить с массива (или 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 эти типы функций могут использоваться в метапрограммировании или в качестве границ массива и т. д.

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

Edit:

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

из выступления Страуструп в "родных 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;

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

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) is действительно evauled во время компиляции!

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

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

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

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

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

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
static const int x = 5;
int arr[x];

over

int arr[5];

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

все остальные ответы отличные, я просто хочу дать классный пример, что можно сделать с помощью constexpr, что удивительно. См. - 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;
}

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

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

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

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

добавлять constexpr:

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

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

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

// constants:
const int MeaningOfLife = 42;

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

int some_arr[MeaningOfLife()];

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