2016-08-09 89 views
3

Warum der C++ Compiler ermöglicht es, eine Funktion als constexpr zu erklären, die constexpr werden können?Warum wird der C++ Compiler ermöglicht es, eine Funktion als constexpr zu erklären, die nicht constexpr werden kann?

Zum Beispiel: http://melpon.org/wandbox/permlink/AGwniRNRbfmXfj8r

#include <iostream> 
#include <functional> 
#include <numeric> 
#include <initializer_list> 

template<typename Functor, typename T, size_t N> 
T constexpr reduce(Functor f, T(&arr)[N]) { 
    return std::accumulate(std::next(std::begin(arr)), std::end(arr), *(std::begin(arr)), f); 
} 

template<typename Functor, typename T> 
T constexpr reduce(Functor f, std::initializer_list<T> il) { 
    return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f); 
} 

template<typename Functor, typename T, typename... Ts> 
T constexpr reduce(Functor f, T t1, Ts... ts) { 
    return f(t1, reduce(f, std::initializer_list<T>({ts...}))); 
} 

int constexpr constexpr_func() { return 2; } 

template<int value> 
void print_constexpr() { std::cout << value << std::endl; } 

int main() { 
    std::cout << reduce(std::plus<int>(), 1, 2, 3, 4, 5, 6, 7) << std::endl; // 28 
    std::cout << reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7}) << std::endl;// 28 

    const int input[3] = {1, 2, 3}; // 6 
    std::cout << reduce(std::plus<int>(), input) << std::endl; 

    print_constexpr<5>(); // OK 
    print_constexpr<constexpr_func()>(); // OK 
    //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error 

    return 0; 
} 

Ausgang:

28 
28 
6 
5 
2 

Warum Fehler in dieser Zeile: //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // errorauch für C++ 14 und C++ 1z?

Warum erlaubt der Compiler das Markieren von reduce() als constexpr, aber reduce() kann nicht als Vorlagenparameter verwendet werden Wenn alle Parameter zur Kompilierungszeit an reduce() übergeben wurden?


Der gleiche Effekt für einige Compiler - die unterstützt C++ 14-std=c++14:

Für alle diese Fälle zusammenstellen OK, bis ungenutzte Linie: //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error

+3

"nennen _der C++ Compiler_ "? Welcher Compiler? GCC? Clang? MSVC? Welche Version? –

Antwort

4

Gehen wir gerade von ihm den Vorschlag, www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235. pdf in Abschnitt 4.1, dritter Absatz: ich zitiere:

Ein konstanter Ausdruck Funktion kann mit nicht konstanten Ausdrücke genannt werden, in diesem Fall ist es nicht erforderlich, dass der resultierende Wert zum Zeitpunkt der Kompilierung ausgewertet werden .

Sehen Sie diese Frage: When does a constexpr function get evaluated at compile time?

template<typename Functor, typename T> 
T constexpr reduce(Functor f, std::initializer_list<T> il) { 
    return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f); 
} 

Wieder, wie Sie wissen, std::accumulate kein constexpr Funktion ist.

template<int value> 
void print_constexpr() { std::cout << value << std::endl; } 

Wieder, wie Sie wissen, non-type template arguments must be constant expressions.Jetzt


:

template<typename Functor, typename T> 
T constexpr reduce(Functor f, std::initializer_list<T> il) { 
    return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f); 
} 

Wie, warum es funktioniert: Hier ist, was der C++ Standard zu sagen hat:

[dcl.constexpr/6] (Hervorhebung von mir):

Wenn die instanziierte Template-Spezialisierung einer constexpr-Funktion templ aß oder Elementfunktion einer Klasse Vorlage scheitern würde die requirements for a constexpr function oder constexpr Konstruktor gerecht zu werden, dass Spezialisierung ist immer noch eine constexpr Funktion oder constexpr Konstruktor obwohl ein Aufruf an eine solche Funktion nicht in ein konstanter Ausdruck erscheinen können ...

Hinweis: that

eine Funktion aus einer Funktion templa instanziiert te heißt Funktion Template-Spezialisierung;


Wenn es nicht eine Vorlage, wird es fehlschlagen:

int constexpr reduce(int(*f)(int, int), std::initializer_list<int> il) { 
    return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f); 
} 

Der Compiler wird jetzt beschweren sich, dass Sie nicht eine nicht constexpr Funktion in einer Funktion definiert als constexpr

3

Wenn Sie dies schreiben code:

constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7}); 

Sie werden sehen, dass reduzieren nicht zu einem consExpr Ergebnis führt.

Der Grund ist, weil "Anmerkung: Nicht-constexpr Funktion '> akkumuliert' kann nicht in einem konstanten Ausdruck verwendet werden" Und wie man hier sehen kann - http://en.cppreference.com/w/cpp/algorithm/accumulate

std :: accumulate ist constexpr nicht

EDIT erweitern, um die eigentliche Frage zu beantworten, danke @peterchen: Es ist kompiliert, wenn es die Verwendung trifft, es nicht und konnte nicht versuchen, und die Funktion auflösen, bis es die spezifische Version der Vorlage kompiliert. Wenn es die Verwendung trifft und das Kompilieren auslöst, löst es das Akkumulieren auf und sieht, dass es kein Constexpr ist, und gibt daher einen Fehler aus.

+2

Vielen Dank! Ja, ich weiß es. Aber warum macht der C++ - Compiler es möglich, eine Funktion als constexpr 'reduce()' zu deklarieren - wenn es nie constexpr ist? – Alex

+1

Es ist kompiliert, wenn es die Verwendung trifft, es nicht und konnte nicht versuchen, die Funktion aufzulösen, bis es die bestimmte Version der Vorlage kompiliert. Wenn es die Verwendung trifft und die Kompilierung auslöst, löst es die Akkumulation auf und sieht, dass es nicht constexpr ist, und gibt daher einen Fehler aus. –

+1

@TheSombreroKid: das ist der Schlüssel - vielleicht sollten Sie es zu Ihrer Antwort hinzufügen. – peterchen

3

Warum ermöglicht es der C++ - Compiler, eine Funktion als conexpr zu deklarieren, die nicht conexpr sein kann?

Es tut es nicht. Sie definieren jedoch keine Funktion constexpr. Sie definieren eine Vorlage.

Lassen Sie uns ein:

struct Is_constexpr { 
    constexpr Is_constexpr() = default; 
    constexpr auto bar() { 
    return 24; 
    } 
}; 

struct Not_constexpr { 
    auto bar() { 
    return 24; 
    } 
}; 

Nun, wenn Sie versuchen, eine Funktion (kein Template) als constexpr zu definieren, die Not_constexpr der Compiler verwendet werden Sie nicht lassen:

constexpr auto foo_function(Not_constexpr v) 
{ 
    return v.bar(); 
    // error: call to non-constexpr function 'auto Not_constexpr::bar()' 
} 

Sie sind jedoch eine Vorlage definieren. Mal sehen, wie das geht:

template <class T> 
constexpr auto foo(T v) 
{ 
    return v.bar(); 
} 

Der Compiler können Sie dies tun. Kein Fehler. Warum? Weil es eine Vorlage ist. Einige instantiations sein kann constexpr, andere nicht, je nach T:

int main() { 
    constexpr Is_constexpr is_c; 
    constexpr Not_constexpr not_c; 

    std::integral_constant<int, foo(is_c)> a; // OK 
    //std::integral_constant<int, foo(not_c)> a; // ERROR 

}