2015-06-29 12 views
5

In diesem Beispiel wird die Methode const nie aufgerufen. Wenn der Code komisch aussieht, hier ist, was ich versuche zu tun:Funktion Überladen mit Std :: Funktion Argument: Warum wird die Const-Methode nie aufgerufen?

Anstelle einer Getter-Methode, lassen Sie Clients Objekte durch Übergabe einer Funktion iterieren. Um sowohl den const als auch den nichtkonstanten Zugriff zu ermöglichen, möchte ich const und non-const overloads bereitstellen. Um die Kopie & Paste zu vermeiden, möchte ich außerdem die nicht-konstante Methode im Sinne der Methode const implementieren: Die Methode const in meinem Code ist tatsächlich komplizierter als die, die ich hier verwende.

Jetzt meine Fragen ist: Wenn Sie diesen Code ausführen, wird es die nicht-const-Funktion rekursiv aufrufen, bis der Stapel überläuft. Ich verstehe nicht, warum die Zeile doStuff([&func](const string *str) in der non-const-Methode sich selbst nennt und nicht die const-Methode.

Antwort

1

Die const Überladung wird nur aufgerufen, wenn eine Instanz der A Klasse const ist. Dies ist der Fall, weil die const am Ende der Deklaration:

void doStuff(function<void (const string *)> func) const //This const at the end causes 
                 //The member function to only be 
                 //called when `a` is const 

CONST-Qualifier am Ende gilt für das this Objekt, nicht auf die Parameter. Das Entfernen der const wird Mehrdeutigkeit verursachen, also verwenden Sie StenSoft, um es zum Laufen zu bringen.

+0

das Entfernen von 'const' würde zu Mehrdeutigkeiten führen –

+0

Danke. Ich war mir nicht sicher. Ich werde das bearbeiten. – JKor

6

Die nichtkonstante Methode wird als akzeptierende Funktion deklariert, die mit string * Argument aufgerufen werden kann. Die bereitgestellte Funktion akzeptiert const string *. string * ist implizit in const string* umwandelbar. Daher ist die Funktion mit const string * ein akzeptables Argument für die nicht-konstante Methode und die nicht-konstante Methode ist ausgewählt, weil this ebenfalls nicht-konstant ist.

Verwenden const_cast auf this die konstante Methode zu verwenden:

const_cast<const A*>(this)->doStuff(…); 
+0

Sehr nette Antwort. Ich hatte das Gefühl, dass mir so etwas gefehlt hat, aber ich konnte es nicht genau sagen. Vielen Dank! – Leander

1

Ich glaube nicht, dass dies eine Überlastung korrekt ist:

void doStuff(function<void (const string *)> func) const 

und

void doStuff(function<void (string *)> func) 

Überladene Funktionen sollten haben denselben Prototyp, aber unterschiedliche Argumente. In Ihrem Fall kann Ihre const-Methode nur aufgerufen werden, wenn Ihr Objekt const ist. Hier haben Sie nur 2 Methoden, die in verschiedenen Situationen aufgerufen werden können, aber diese Situationen werden nicht durch den Mechanismus overloading oder eine andere argumentabhängige Funktion verursacht.

Warum sollten Sie auch nicht zulassen, dass Personen Ihr Objekt mit Standard-Iteratoren verwenden? Implementieren Sie begin() und end() Methoden in Ihrem Objekt und lassen Sie Leute alles tun, was sie wollen, ohne Ihre Schnittstelle aber Standard-Lib-Schnittstelle: Sie werden in der Lage sein zu verwenden, einige Algorithmen wie find und find_if und sonst gute Dinge.

+0

Sie haben Recht, dass dies nicht formell überladen ist, danke, dass Sie darauf hingewiesen haben. Lebe und lerne. :) Ich habe darüber nachgedacht, begin() und end() zu implementieren, aber meine Klasse hat mehr als eine Sammlung, die möglicherweise über iteriert werden könnte, und ich möchte das explizit machen. – Leander

1

Metaprogrammierung vorformulierten:

template<template<class...>class Z, class always_void, class...Ts> 
struct can_apply_helper:std::false_type{}; 
template<template<class...>class Z, class...Ts> 
struct can_apply_helper<Z, 
    decltype((void)(Z<Ts...>)), 
Ts...>:std::true_type{}; 
template<template<class...>class Z, class...Ts> 
using can_apply=can_apply_helper<Z,void,Ts...>; 

Charakterzug, wenn ein Typ-Ausdruck erkennt einen gültigen Aufruf darstellen würde:

// result_of_t fails to be SFINAE in too many compilers: 
template<class F, class...Ts> 
using invoke_helper_t=decltype(std::declval<F>()(std::declval<Ts>()...)); 
template<class Sig> struct can_invoke; 
template<class F, class...Ts> 
struct can_invoke<F(Ts...)>: 
    can_apply<invoke_helper_t, F, Ts...> 
{}; 

Jetzt, Ersatz doStuff in Ihrer Klasse. Die erste erkennt, wenn Sie die Funktion mit einem std::string const* anrufen:

template<class F, class=std::enable_if_t< 
    can_invoke< F&(std::string const*) >{} 
>> 
void doStuff(F&& func) const 
{ 
    cout << "Const method called" << endl; 
    for(const auto& i_string : m_vec) 
     func(i_string); 
} 

Dieser erkennt, ob Sie nicht nennen es mit einem std::string const* und dass Sie können nennen es mit einem std::string*:

template<class F, class=std::enable_if_t< 
    !can_invoke< F&(std::string const*) >{} && 
    can_invoke< F&(std::string*) >{} 
>> 
void doStuff(F&& func) 
{ 
    cout << "Non-const method called" << endl; 
    doStuff([&func](const string *str) 
    { 
     auto mutableString = const_cast<string *>(str); 
     func(mutableString); 
    }); 
} 

Dies entfernt auch die unnötige Art Löschung von std::function in Ihrem Beispiel und leitet jeden Anruf, der an die const Methode gehen kann, an die const Methode.

Nebenbei ist das Speichern einer std::vector<std::string*> fast immer eine schlechte Idee.

+0

Um ehrlich zu sein, habe ich mich nicht wirklich mit fortgeschrittenen Metaprogrammierungsmethoden beschäftigt, und das ist mir momentan etwas zu komplex - und ich kann mir auch andere pragmatische Lösungen für mein Problem vorstellen, wie zum Beispiel die Methoden einfach umzubenennen. Aber du hast mir etwas zum Nachdenken gegeben, danke! – Leander

+0

@Leander * nicken *. Wenn concepts-lite es jemals in C++ schafft, wird vieles von dem oben genannten lächerlich einfacher und wir brauchen es, um es einfacher zu machen. – Yakk

1

Ihre Argumentation kann sein, dass ein Lambda mit der Signatur void(const string *) nicht mit einer string * aufgerufen werden kann und daher nicht in eine function<void (string *)> umgewandelt werden kann. Dies ist inkorrekt, da eine string * implizit in eine const string * konvertierbar ist und es daher vollkommen legitim ist, ein function<void (string *)> Objekt aus einem Funktionsobjekt zu konstruieren, das eine const string * erwartet. Es ist nur der umgekehrte Fall, der nicht erlaubt ist. Dies bedeutet, dass der Compiler feststellt, dass beide Argumentkonvertierungen lebensfähig sind (mit gleichem Rang). Dies würde die beiden Kandidaten in der Überladungsauflösung mehrdeutig machen, aber da der implizite Wert this nicht-const ist, wird die Überladung bevorzugt (der Rang des impliziten this ist "exakte Übereinstimmung" für Nicht-const vs. "für const).

Die Lösung, wie zuvor erwähnt worden ist dafür, dass die pointee impliziten thisconst ist. Dies wird die Überladung aus der Kandidatenmenge eliminieren und einen Aufruf der beabsichtigten Überladung erzwingen.