2014-03-04 8 views
5

Im folgenden wird static constexpr Element L in Klasse A initialisiert und dann durch Wert oder (universelle) Referenz übergeben. Letzteres schlägt in Clang, aber nicht in GCC fehl, und das Verhalten ist für Member-/Nicht-Member-Funktionen leicht unterschiedlich. Im Detail:eine statische constexpr-Variable durch universelle Referenz übergeben?

#include <iostream> 

using namespace std; 

struct A 
{ 
    static constexpr size_t L = 4; 

    template <typename T> 
    void member_ref(T&& x) { cout << std::forward<T>(x) << endl; } 

    template <typename T> 
    void member_val(T x) { cout << x << endl; } 
}; 

template <typename T> 
void ref(T&& x) { cout << std::forward<T>(x) << endl; } 

template <typename T> 
void val(T x) { cout << x << endl; } 

int main() 
{ 
    A().member_ref(A::L); // Clang: linker error: undefined reference to `A::L' 
    A().member_val(A::L); // fine (prints 4) 
    ref(A::L);    // Clang: compiles/links fine, no output 
    val(A::L);    // fine (prints 4) 
} 

Nach einigen Experimenten in das Problem von einem größeren Programm zu isolieren, wurde mir klar, dass ich aus Versehen der Adresse einer constexpr Variable verwenden, obwohl ich in dem Wert nur interessiert.

Ich möchte (universelle) Verweis übergeben, so dass der Code generisch ist und mit großen Strukturen ohne zu kopieren funktioniert. Ich dachte, du könntest alles mit einer universellen Referenz durchgehen, aber das scheint hier nicht der Fall zu sein. Ich kann keine separate (out-of-class) Definition für L verwenden, da dies Teil einer Nur-Header-Bibliothek ist.

So kann man Abhilfe seinen Wert bei Aufruf zu erzeugen, das heißt sagen size_t(A::L) oder so etwas wie A::get_L() statt nur A::L, wo (in Klasse A)

static constexpr size_t get_L() { return L; } 

aber beide Lösungen sehen ein bisschen unbeholfen. In meinem tatsächlichen Code wird der Anruf innerhalb der Klasse gemacht und sieht wie call(0, L, ...) aus, der ziemlich unschuldig erscheint (0, L sehen wie Werte aus). Ich möchte den Anruf so einfach wie möglich halten.

Ich denke this question und its follow-up ziemlich viel zu erklären, was passiert. Könnte also jemand vorschlagen, was wäre der sauberste Weg, damit umzugehen?

+0

Eigentlich ist dies unabhängig vom gesamten rvalue/universellen Referenzgeschäft. Der Punkt ist, dass Sie ODR verwenden "A :: L" ohne Angabe einer Definition, und das ist ein Problem mit Lvalue Referenzen. In der Tat müssen Sie für "constexpr" (aber nicht für "const") immer eine "out-of-class" -Definition über den In-Class-Initialisierer bereitstellen. Retagging entsprechend. – TemplateRex

+0

Danke für die erneute Markierung. Ich wusste, Lvalue Referenzen hatten das gleiche Problem, ich wollte nur meine Motivation geben. – iavr

+0

@TemplateRex: Nein, nicht * immer *. Nur wenn das Mitglied odr-used ist. Siehe 9.4.2p3. "...Das Element muss immer noch in einem Namensraumbereich definiert werden, wenn es im Programm verwendet wird und die Namensbereichsdefinition nicht einen Initialisierer enthalten soll. " –

Antwort

4

Sie benötigen A::L außerhalb seiner Klasse in einer Quelldatei

constexpr size_t A::L; 

Live example mit Clang

Für Kopf nur Code zu definieren, und wenn Sie Ihre Klasse A nicht bereits eine Vorlage ist, können Sie festlegen, eine Klassenvorlage A_<T> mit einem void Standardwert und ein typedef für A in Bezug auf die

template<class = void> 
struct A_ 
{ 
    static constexpr size_t L = 4; 

    template <typename T> 
    void member_ref(T&& x) { cout << std::forward<T>(x) << endl; } 

    template <typename T> 
    void member_val(T x) { cout << x << endl; } 

}; 

template<class T> 
constexpr size_t A_<T>::L; 

using A = A_<>; 
schreiben

Live Example.

HINWEIS: Dieses Geschäft kann eine beträchtliche Menge an Boiler-Plate beinhalten. Es ist gut zu beachten, dass ein

template 
< 
    class MyConcept1, 
    class MyConcept2, 
    class YetAnotherConcept 
    // more long and well-documented template parameter names 
> 
struct A 
{ 
    // many static constexpr variabels 
}; 

template<class P1, class P2, class P3 /* many more short parameter names */> 
constexpr SomeType A<P1, P2, P3, /* many more */>::some_var; 

// many more one-liners. 

Template-Parameter haben nur formale Namen schreiben kann, sie müssen nicht überall das gleiche sein (nur setzt sich in der richtigen Reihenfolge überall, wenn auch!).

+0

Danke, aber wie gesagt in meiner Frage kann ich das nicht (Header-only-Bibliothek). – iavr

+0

@iavr OK, in diesem Fall können Sie die Klassenvorlage Trick und eine Typedef verwenden, um eine zu definieren Spezialfall für die Klasse, die Sie verwenden wollen, das passt alles in eine Kopfzeile – TemplateRex

+0

Danke, ich kannte diesen Trick nicht, leider kann die Klasse 'A' 10 weitere Vorlagenparameter haben und ich kann 2-3 haben Werte wie "L", so dass sogar das Wiederholen des gesamten Template-Materials 2-3 mal nicht in Frage kommt (weit unbeholfener als ein intermediärer Call-by-Value). – iavr