2015-06-29 8 views
5
#include <functional> 
#include <iostream> 
#include <string> 
#include <vector> 

using namespace std; 

class A 
{ 
    public: 
    void doStuff(function<void (const string *)> func) const 
    { 
     cout << "Const method called" << endl; 
     for(const auto& i_string : m_vec) 
      func(i_string); 
    } 

    void doStuff(function<void (string *)> func) 
    { 
     cout << "Non-const method called" << endl; 
     doStuff([&func](const string *str) 
     { 
      auto mutableString = const_cast<string *>(str); 
      func(mutableString); 
     }); 
    } 

private: 
    vector<string *> m_vec; 
}; 

int main() 
{ 
    auto a = A{}; 

    a.doStuff([](string *str){ 
     *str = "I modified this string"; 
    }); 
} 

W tym przykładzie metoda const nigdy nie jest wywoływana. Jeśli kod wygląda dziwnie, oto, co próbuję zrobić:Funkcja przeciążania argumentem std :: function: dlaczego metoda const nie jest wywoływana?

Zamiast metody getter, pozwalam klientom iterować obiekty, przekazując funkcję. Aby włączyć dostęp zarówno stały, jak i niestanowiący stałej, chcę zapewnić przeciążenia const i non const. Ponadto, w celu uniknięcia wklejenia kopii, chcę zaimplementować metodę non const z punktu widzenia metody const: metoda const w moim kodzie jest w rzeczywistości bardziej skomplikowana niż ta, której tu używam.

Teraz moje pytania są następujące: Jeśli uruchomisz ten kod, wywoła on rekurencyjnie funkcję non const, dopóki stos nie przepełni się. Nie rozumiem, dlaczego linia doStuff([&func](const string *str) w metodzie non-const wywołuje samą siebie, a nie metodę const.

Odpowiedz

1

Przeciążenie const jest wywoływane tylko wtedy, gdy wystąpienie klasy A to const. Dzieje się tak ze względu na const na końcu deklaracji:

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-kwalifikator na końcu odnosi się do przedmiotu this, nie do parametrów. Usunięcie const spowoduje niejednoznaczność, więc użyj sposobu StenSoft, aby go uruchomić.

+0

usunięcie 'const' spowodowałoby niejasności –

+0

Dzięki. Nie byłem pewien. Zmienię to. – JKor

6

Metoda niestałości jest zadeklarowana jako funkcja akceptująca, która może być wywołana z argumentem string *. Podana funkcja akceptuje const string *. string * jest implicitely convertible na const string*. Dlatego funkcja z const string * jest akceptowalnym argumentem dla metody niestałej i wybrano metodę niestałą, ponieważ this również nie jest const.

Zastosowanie const_cast na this używać const metody:

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

Bardzo ładna odpowiedź. Miałem wrażenie, że czegoś mi brakuje, ale nie mogłem tego położyć. Dzięki! – Leander

1

Nie sądzę, że jest to poprawne przeciążenie:

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

i

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

Przeciążone funkcje powinny mają ten sam prototyp, ale różne argumenty. W twoim przypadku twoja metoda const może zostać wywołana tylko wtedy, gdy twój obiekt jest const. Tutaj masz tylko 2 metody, które można wywoływać w różnych sytuacjach, ale te sytuacje nie są spowodowane przez mechanizm overloading lub inną zależną od argumentów funkcję.

Ponadto, dlaczego nie pozwolić ludziom korzystać z obiektu z domyślnymi iteratorami? Wprowadź w swoim obiekcie metody begin() i end() i pozwól, aby ludzie robili wszystko, co chcą, bez interfejsu, ale z interfejsem std lib: będą w stanie używać różnych algorytmów, takich jak find i find_if i innych dobrych rzeczy.

+0

Masz rację, że to nie jest formalne przeciążanie, dzięki za wskazanie tego. Żyj i ucz się. :) Rozważałem implementację begin() i end(), ale moja klasa ma więcej niż jedną kolekcję, która mogłaby być potencjalnie iterowana, i chcę to jasno wyrazić. – Leander

1

METAPROGRAMOWANIE boilerplate:

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...>; 

cecha, która wykrywa, czy wyrażenie typu stanowiłoby ważny połączenia:

// 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...> 
{}; 

Teraz wymiana doStuff w swojej klasie. Pierwszy z nich wykrywa czy można wywołać funkcję z std::string const*:

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

Ten wykrywa jeśli nie może nazwać z std::string const* i że może nazwać z 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); 
    }); 
} 

to również usuwa zbędne wymazywanie typu z std::function w twoim przykładzie i trasuje wszystkie połączenia, które można przejść do metody const do metody const.

Na marginesie, przechowywanie std::vector<std::string*> jest prawie zawsze złym pomysłem.

+0

Szczerze mówiąc, tak naprawdę nie dostałem się do bardziej zaawansowanych metod metaprogramowania i jest to trochę zbyt skomplikowane dla mnie teraz - i mogę też myśleć o innych pragmatycznych rozwiązaniach mojego problemu, takich jak zmiana nazwy metod. Ale dałeś mi coś do myślenia, dziękuję! – Leander

+0

@Leander * skinieniem *. Jeśli concept-lite kiedykolwiek przechodzi do C++, wiele z powyższych staje się absurdalnie łatwiejsze i potrzebujemy, aby było łatwiej. – Yakk

1

Twoje rozumowanie może polegać na tym, że lambda z podpisem void(const string *) nie może być wywołana z string *, a zatem nie jest konwertowana na function<void (string *)>. Jest to niepoprawne, ponieważ string * jest domyślnie wymienialne na const string * i dlatego jest całkowicie uzasadnione, aby skonstruować obiekt function<void (string *)> z obiektu funkcji, oczekując, że będzie to const string *. Jest to tylko awers, który nie jest dozwolony. Oznacza to, że kompilator określa, że ​​obie konwersje argumentów są wykonalne (z równą rangą). To sprawiłoby, że dwaj kandydaci byliby niejednoznaczni w rozdzielczości przeładowania, ale ponieważ osoba wskazana jako niejawna this nie jest const, preferowane jest przeciążenie inne niż const (ranga niejawnego this jest "ścisłe dopasowanie" dla nie-const w porównaniu do "konwersji "dla const).

Rozwiązaniem, o którym wcześniej wspomniano, jest upewnienie się, że osoba wskazana jako domyślna this to const. To wyeliminuje przeciążenie z zestawu kandydującego i wymusi wywołanie zamierzonego przeciążenia.

Powiązane problemy