Kiedy należy używać funkcji constexpr w C++11?

Wydaje mi się, że posiadanie "funkcji, która zawsze zwraca 5" łamie lub osłabia znaczenie "wywołania funkcji". Musi być jakiś powód, albo potrzeba takiej możliwości, bo inaczej nie byłoby jej w C++11. Dlaczego tam jest?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

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

Wydaje mi się, że gdybym napisał funkcję zwracającą literalną wartość i przyszedł do przeglądu kodu, ktoś powiedziałby mi, powinienem wtedy zadeklarować stałą wartość zamiast pisać return 5.

Author: TemplateRex, 2011-01-20

13 answers

Przypuśćmy, że robi to coś bardziej skomplikowanego.

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

const int meaningOfLife = MeaningOfLife( 6, 7 );

Teraz masz coś, co można ocenić do stałej, zachowując dobrą czytelność i umożliwiając nieco bardziej złożone przetwarzanie niż ustawianie stałej na liczbę.

Zasadniczo zapewnia dobrą pomoc w utrzymaniu, ponieważ staje się bardziej oczywiste, co robisz. Weźmy max( a, b ) na przykład:

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

To dość prosty wybór, ale oznacza to, że jeśli zadzwonisz max z wartości stałe jest jawnie obliczany w czasie kompilacji, a nie w czasie wykonywania.

Innym dobrym przykładem może być DegreesToRadians Funkcja. Każdy znajdzie stopnie łatwiejsze do odczytania niż radiany. O ile wiadomo, że 180 stopni jest w radianach, o wiele jaśniej jest napisane w następujący sposób:

const float oneeighty = DegreesToRadians( 180.0f );

Wiele dobrych informacji tutaj:

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

 259
Author: Goz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-07 19:04:42

Wprowadzenie

constexpr nie został wprowadzony jako sposób, aby powiedzieć implementacji, że coś może być oceniane W kontekście, który wymaga stałe wyrażenie; Zgodne implementacje były w stanie udowodnić to przed C++11.

Coś, czego implementacja nie może udowodnić, to intencja pewnego fragmentu kodu:

  • Co to jest, że deweloper chce wyrazić z tym podmiotem?
  • Czy powinniśmy ślepo zezwolić kodowi na być używane w stałej-wyrażenie , tylko dlatego, że to działa?

Czym byłby świat bez constexpr?

Załóżmy, że rozwijasz bibliotekę i zdajesz sobie sprawę, że chcesz być w stanie obliczyć sumę każdej liczby całkowitej w przedziale (0,N].

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

Brak intencji

Kompilator może łatwo udowodnić, że powyższa funkcja jest wywoływalna w stałym wyrażeniu, jeśli przekazany argument jest znany podczas tłumaczenia; ale nie zadeklarowałeś tego jako intencji - tak się po prostu stało.

Teraz pojawia się ktoś inny, czyta twoją funkcję, robi tę samą analizę co kompilator; " Oh, ta funkcja jest użyteczna w wyrażeniu stałym!", i pisze następujący fragment kodu.

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

Optymalizacja

Ty, jako programista "awesome", decydujesz, że f powinien buforować wynik podczas wywoływania; kto chciałby obliczyć ten sam zestaw wartości w kółko?

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

Wynik

Wprowadzając swoją głupią optymalizację, złamałeś każde użycie swojej funkcji, które zdarzyło się w kontekście, w którym wymagane było wyrażenie stałe.

Nigdy nie obiecałeś, że funkcja będzie użyteczna w wyrażeniu stałym , A Bez constexpr nie będzie sposobu dostarczenia takiej obietnicy.


Więc po co nam constexpr?

Podstawowe użycie constexpr ma zadeklarować zamiar .

Jeśli encja nie jest oznaczona jako constexpr - nigdy nie była przeznaczona do użycia w wyrażeniu stałym ; a nawet jeśli tak jest, polegamy na kompilatorze do diagnozowania takiego kontekstu (ponieważ lekceważy on nasze intencje).

 102
Author: Filip Roséen - refp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-11 21:43:38

Weź std::numeric_limits<T>::max(): z jakiegokolwiek powodu, jest to metoda. Byłoby to korzystne tutaj.

Inny przykład: chcesz zadeklarować tablicę C (lub std::array), która jest tak duża jak inna tablica. Sposób na to w tej chwili jest taki:

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

Ale czy nie byłoby lepiej umieć pisać:

int y[size_of(x)];

Dzięki constexpr możesz:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}
 82
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 14:32:20

constexpr funkcje są naprawdę ładne i świetny dodatek do c++. Masz jednak rację, że większość problemów, które rozwiązuje, może być nieelegancko rozwiązywana za pomocą makr.

Jednak jedno z zastosowań constexpr nie ma odpowiedników C++03, wpisanych stałych.

// 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;
 15
Author: deft_code,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-16 19:00:48

Z tego co czytałem, potrzeba constexpr wynika z problemu w metaprogramowaniu. Klasy cech mogą mieć stałe reprezentowane jako funkcje, think: numeric_limits:: max(). Z constexpr, te typy funkcji mogą być używane w metaprogramowaniu, lub jako granice tablic, itd itd.

Innym przykładem poza moją głową byłoby to, że dla interfejsów klas, możesz chcieć, aby typy pochodne definiowały własne stałe dla jakiejś operacji.

Edit:

Po grzebaniu w Wygląda na to, że inni wymyślili jakieś przykłady tego, co może być możliwe dzięki constexprs.

 14
Author: luke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:32

Z wypowiedzi Stroustrupa na "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
 10
Author: user2176127,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-16 03:16:01

Innym zastosowaniem (jeszcze nie wspomnianym) są konstruktory constexpr. Pozwala to na tworzenie stałych czasu kompilacji, które nie muszą być inicjowane podczas wykonywania.

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

Sparuj to z literałami zdefiniowanymi przez użytkownika i uzyskaj pełną obsługę klas zdefiniowanych przez użytkownika.

3.14D + 42_i;
 6
Author: Motti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 18:16:59

Kiedyś istniał wzór z metaprogramowaniem:

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

Wydaje mi się, że constexpr został wprowadzony, aby umożliwić pisanie takich konstrukcji bez konieczności stosowania szablonów i dziwnych konstrukcji ze specjalizacją, SFINAE i takimi tam - ale dokładnie tak, jak piszesz funkcję run-time, ale z gwarancją, że wynik będzie określony w czasie kompilacji.

Należy jednak pamiętać, że:

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

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

Skompiluj to z g++ -O3 a zobaczysz, że fact(10) jest rzeczywiście evauled w czas kompilacji!

Kompilator obsługujący VLA (więc kompilator C w trybie C99 lub kompilator C++ z rozszerzeniami C99) może nawet pozwolić na:
int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Ale to, że w tej chwili jest to niestandardowe C++ - constexpr wygląda na sposób walki z tym (nawet bez VLA, w powyższym przypadku). I nadal jest problem konieczności posiadania" formalnych " stałych wyrażeń jako argumentów szablonowych.

 5
Author: Kos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 18:45:37

Właśnie rozpoczęliśmy przełączanie projektu na c++11 i natknęliśmy się na zupełnie dobrą sytuację dla constexpr, który czyści alternatywne metody wykonywania tej samej operacji. Najważniejsze jest to, że można umieścić funkcję w deklaracji rozmiaru tablicy tylko wtedy, gdy jest zadeklarowana constexpr. Istnieje wiele sytuacji, w których widzę, że jest to bardzo przydatne, idąc naprzód w obszarze kodu, w którym jestem zaangażowany.

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

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
 5
Author: jgibbs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-04 07:02:23

Twój podstawowy przykład służy temu samemu argumentowi, co same stałe. Dlaczego używać

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

Over

int arr[5];

Ponieważ jest o wiele łatwiejsze do utrzymania. Korzystanie z constexpr jest znacznie, znacznie szybsze do zapisu i odczytu niż istniejące techniki metaprogramowania.

 1
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 16:23:52

Wszystkie inne odpowiedzi są świetne, chcę tylko dać fajny przykład jednej rzeczy, którą można zrobić z constexpr, która jest niesamowita. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) jest parserem HTML w czasie kompilacji i silnikiem szablonów. Oznacza to, że możesz umieścić HTML i wydostać się z drzewa, które można manipulować. Parsowanie w czasie kompilacji może dać ci trochę dodatkowej wydajności.

Ze strony github przykład:

#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;
}
 1
Author: Halcyon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-24 12:23:18

Może umożliwić kilka nowych optymalizacji. const tradycyjnie jest podpowiedzią dla systemu typów i nie może być użyta do optymalizacji (np. Funkcja const member może const_cast i modyfikować obiekt tak czy inaczej, zgodnie z prawem, więc const nie może być zaufana do optymalizacji).

constexpr oznacza, że wyrażenie rzeczywiście jest stałe, pod warunkiem, że dane wejściowe do funkcji są const. Consider:

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

Jeśli zostanie to ujawnione w innym module, kompilator nie może ufać, że GetNumber() nie zwróci różne wartości za każdym razem, gdy jest wywoływana - nawet po kolei, bez wywołań non-const pomiędzy-ponieważ const mogła zostać odrzucona w implementacji. (Oczywiście każdy programista, który to zrobił powinien zostać zastrzelony, ale język na to pozwala, dlatego kompilator musi przestrzegać zasad.)

Dodanie constexpr:

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

Kompilator może teraz zastosować optymalizację, w której wartość zwracana GetNumber() jest buforowana i eliminuje dodatkowe wywołania GetNumber(), ponieważ constexpr jest silniejszym gwarancja, że wartość zwrotu nie ulegnie zmianie.

 0
Author: AshleysBrain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 17:09:53

Przydaje się do czegoś takiego jak

// constants:
const int MeaningOfLife = 42;

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

int some_arr[MeaningOfLife()];

Połącz to z klasą cech lub tym podobnym, a stanie się całkiem przydatne.

 -2
Author: plivesey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-14 05:56:23