2016-07-04 21 views
2

Ich möchte eine Template-Schnittstelle entwerfen, bei der sich die Konstante der Funktion und der Rückgabetyp je nach Template-Parameter ändern. Ich habe es geschafft, dies für den Rückgabetyp wie folgt zu tun.C++ Templates zum Ändern der Konsistenz einer Funktion

Kann ich irgendwie vermeiden, zwei const/non-const Funktionen hier zur Kompilierzeit mit einigen SFINAE Magie? std :: enable_if wäre hier ideal gewesen, aber es scheint mir, dass const kein Typ ist und dieser Ansatz möglicherweise nicht funktioniert. Irgendwelche Vorschläge?

+0

Warum nicht nur die 'const' Version behalten, wenn Sie nicht überladen müssen? – KABoissonneault

+0

@KABoissonneault Ich brauche beide Versionen, da sich die Konstanz der Vorlage je nach Template-Parameter ändert. Nur um zu verdeutlichen, möchte ich in der Lage sein, bestimmte "Data" -Strukturen zu erstellen, die zu einem späteren Zeitpunkt geändert werden können, während sich einige sicher nicht ändern werden. Daher die Notwendigkeit für beide const/non-const-Funktionalität. – cplusplusrat

Antwort

3

ist ein Beispiel basiert auf Vererbung:

#include <type_traits> 
#include <iostream> 

template<typename T, bool canChange> 
struct Changable { using type = const T; }; 

template<typename T> 
struct Changable<T, true> { using type = std::decay_t<T>; }; 

template<typename, typename, bool> 
struct Base; 

template<typename D, typename T> 
struct Base<D, T, true> { 
    using DataType = typename Changable<T, true>::type; 
    DataType& GetDataRef() { std::cout << "non-const" << std::endl; return static_cast<D*>(this)->m_data; } 
}; 

template<typename D, typename T> 
struct Base<D, T, false> { 
    using DataType = typename Changable<T, false>::type; 
    DataType& GetDataRef() const { std::cout << "const" << std::endl; return static_cast<const D*>(this)->m_data; } 
}; 

template<typename T, bool canChange> 
struct Data: Base<Data<T, canChange>, T, canChange> { 
    friend class Base<Data<T, canChange>, T, canChange>; 
    typename Base<Data<T, canChange>, T, canChange>::DataType m_data{}; 
    using Base<Data<T, canChange>, T, canChange>::GetDataRef; 
}; 

int main() { 
    Data<int, true> d1; 
    Data<int, false> d2; 
    d1.GetDataRef(); 
    d2.GetDataRef(); 
} 

Wie gewünscht Data hat nur eine Definition der GetDataRef Methode.
Welche davon verfügbar ist, die const die eine oder andere, hängt vom Wert canChange ab.

Beachten Sie die friend Deklaration. Dadurch kann die Basisklasse auf die privaten Datenelemente von Data zugreifen.

+1

Nice one! Wie immer unschlagbar! :) –

+1

@ W.F. Um ehrlich zu sein, hat Ihre Antwort den richtigen Ausweg gezeigt. ;-) – skypjack

+1

Das mag ich :). Vielen Dank ! – cplusplusrat

1

Vielleicht so etwas wie dies kann das Problem lösen:

#include <type_traits> 
#include <iostream> 

template<typename T, bool canChange> 
struct Changable: std::false_type { using type = const T; }; 

template<typename T> 
struct Changable<T, true>: std::true_type { using type = std::decay_t<T>; }; 

template<typename T, bool canChange> 
struct Data { 
    using DataTraits = Changable<T, canChange>; 

private: 
    template<typename U> 
    std::enable_if_t<U::value, typename U::type&> 
    GetDataRefImpl() { std::cout << "non const" << std::endl; return m_data; } 

    template<typename U> 
    std::enable_if_t<not U::value, typename U::type&> 
    GetDataRefImpl() const { std::cout << "const" << std::endl; return m_data; } 

public: 
    typename DataTraits::type m_data{}; 

    typename DataTraits::type& GetDataRef() { return GetDataRefImpl<DataTraits>(); } 
    typename DataTraits::type& GetDataRef() const { return GetDataRefImpl<DataTraits>(); } 
}; 

int main() { 
    Data<int, true> d1; 
    Data<int, false> d2; 
    d1.GetDataRef(); 
    d2.GetDataRef(); 
} 

Die Grundidee ist es, sowohl die von der Klasse ausgesetzt Funktionen, so dass sie dann intern weiterleiten an denselben sfinae d ein, die const ist oder non-const (dies hängt vom Wert canChange ab).
Wie Sie, indem Sie das Beispiel sehen kann, ist das Ergebnis:

nicht const
const

Dies gilt auch dann, wenn beide d1 und d2 haben als nicht const definiert.
Die std::enable_if schalten die rechte interne Funktion zur Kompilierzeit ein.

Beachten Sie, dass ich verwendet habe, was die C++ 14 bietet (als Beispiel).
Das Beispiel kann leicht in ein C++ 11 basierend auf (ist nichts anderes als typename std::enable_if<condition, type>::type und so weiter) konvertiert werden. Hier

+0

Sie benötigten weiterhin eine const und non-const Funktion in der öffentlichen Schnittstelle. Es ist nur geringfügig besser als das, was ich bereits habe. Ich habe SO für ähnliche Fragen ausfindig gemacht und bin jetzt zunehmend davon überzeugt, dass es ohne zwei Funktionen in der öffentlichen Schnittstelle nicht möglich ist. – cplusplusrat

+0

@cplusplusrat Sie haben Recht, aber diese Methoden sind nichts weiter als ein Spediteur für die interne * sfinae * d Methode. Darüber hinaus können Sie diese Methode sowohl mit Konstanten als auch mit Konstanten ohne Konstanten verwenden. * Die Konstanz * deiner Klasse ist jetzt erhalten. Eine andere Lösung wäre, die Klasse auf eine "canChange" -Basis zu spezialisieren oder einen Delegaten zu haben, an den die Anfrage weitergeleitet werden kann. – skypjack

2

Ich denke, ich würde dies unter Verwendung der Vorlagen, die bereits in der Standardbibliothek verfügbar sind, angehen. Es erfordert keine Vererbung oder benutzerdefinierte Klassen.

#include <utility> 

template<typename T, bool canChange> 
struct Data{ 
    using value_type = T; 
    using cv_type = std::conditional_t<canChange, value_type, std::add_const_t<value_type>>; 
    using reference = std::add_lvalue_reference_t<cv_type>; 
    using const_reference = std::add_lvalue_reference_t<std::add_const_t<cv_type>>; 

    Data(T t) : m_data(std::move(t)) {} 

    cv_type m_data; //< This makes it const/non-const at compile time. 

    // This function will also make the return type const/non-const 
    // at compile time. 
    reference GetDataRef(){ return m_data;} 

    //However, it seems to me that I still need a second function 
    //with an explicit "const", which I can't seem to avoid. 
    const_reference GetDataRef() const {return m_data;} 
}; 

int main() 
{ 
    Data<int, true> d1 { 10 }; 
    d1.m_data = 12; 
    const Data<int, true>& rd1 = d1; 

    auto& a = d1.GetDataRef(); 
    auto& b = rd1.GetDataRef(); 
    a = 12; // compiles fine 
// b= 12; won't compile 

    Data<int, false> d2 { 10 }; 
    const Data<int, false>& rd2 = d2; 

    auto& c = d2.GetDataRef(); 
    auto& d = rd2.GetDataRef(); 
// c = 12; // won't compile 
// d = 12; // won't compile 

} 

Nun zur Frage:

Kann ich irgendwie vermeiden, dass zwei const/nicht-const-Funktionen hier bei der Kompilierung einige SFINAE Magie mit?

Sie antworten fast Ihre eigene Frage hier. SFINAE erfordert, dass Vorlagenargumente im unmittelbaren Kontext betrachtet werden. Das ist eine komplexe Art zu sagen, dass der Ausdruck in std::enable_if<> von einem bestimmten Schablonentyp abhängen muss.

Leider ist der Schablonentyp von T zu dem Zeitpunkt bekannt, zu dem die Funktion GetDataRef ausgewertet wird, daher wird enable_if uns hier nicht weiterhelfen.

Wenn wir also nur eine Version von GetDataRef wollen, müssten wir tatsächlich auf die Ableitung aus einem Schablonentyp zurückgreifen (die Basisklasse würde dann im unmittelbaren Kontext von T ausgewertet).

Allerdings gibt es auch dann ein Problem.

berücksichtigen:

  • Data<int, true>& x Dies ist ein Hinweis auf veränderbare Behälter Dies ist veränderbare Daten enthalten

  • const Data<int, true>& y ein Verweis auf einen unveränderlichen Behälter enthält veränderbare Daten

Aufruf x.GetDataRef() sollte eine veränderliche Referenz auf ein int zurückgeben, sonst werden wir unsere Benutzer verwirren.

Aufruf y.GetDataRef() sollte sicherlich einen const Verweis auf ein int zurückgeben, sonst wieder, Benutzer können schockiert sein zu erfahren, dass ein Mitglied einer const Sache tatsächlich veränderbar ist.

+0

Chapeau ... sowieso will das OP nur eine Funktion in seiner Schnittstelle, entweder const oder non-const (es hängt von 'canChange' ab). Auf diese Weise haben Sie zwei Definitionen von 'GetDataRef'. (Um klar zu sein, stimme ich Ihnen zu, dass dies der Weg ist, aber ich weiß nicht, was das eigentliche Problem ist, so kann ich nicht beurteilen). – skypjack

+1

@skypjack Ich weiß, dass er denkt, dass er das will, aber ich denke nicht, dass er es durchdacht hat. Antwort aktualisiert –

+1

... _Ich weiß, er denkt, er will das ... Du gewinnst !! :-D – skypjack