2013-05-01 10 views
8

Come posso risolvere il seguente problema?"Il membro è privato" sebbene non acceda dall'esterno, quando si utilizza il tipo di ritorno finale

Sto scrivendo qualche libreria funzionale che definisce le seguenti funzioni che sono rilevanti per questa domanda:

  • call(f,arg): Chiama una funzione con un argomento. Solo un involucro di cui ho bisogno per alcune situazioni.
  • comp(f1,f2): restituisce una composizione di due funzioni. Restituisce un functor helper che rappresenta la composizione delle due funzioni.

L'implementazione è simile al seguente (versioni semplificate che ancora dimostrare il problema):

// Call f with one argument 
template <class Fn, class Arg> 
auto call(const Fn &f, const Arg & arg) -> decltype(f(arg)) { 
    return f(arg); 
} 

// Helper functor for the function below 
template<class Fn1, class Fn2> 
class CompFn { 
    Fn1 a; 
    Fn2 b; 

public: 
    CompFn(const Fn1 &f1, const Fn2 &f2) : a(f1), b(f2) {} 

    template<class Arg> inline 
    auto operator()(const Arg & arg) const -> decltype(call(b, call(a, arg))) { 
     return call(b, call(a, arg)); 
    } 
}; 

/** Composition of f1 and f2 (f2 after f1). */ 
template<class Fn1, class Fn2> 
CompFn<Fn1,Fn2> comp(const Fn1 &f1, const Fn2 &f2) { 
    return CompFn<Fn1,Fn2>(f1, f2); 
} 

Il seguente codice viene utilizzato come un semplice test:

// Example: Take the length of the string and compare it against zero. 
std::function<int(std::string)> stringLength = [](std::string s) { return s.size(); }; 
std::function<bool(int)> greaterZero = [](int x) { return x > 0; }; 
auto stringNotEmpty = comp(stringLength, greaterZero); 

std::string testInput1 = "foo"; 
std::string testInput2 = ""; 

Fin qui, tutto funziona bene. Chiamare lo comp non sembra essere un problema. Anche chiamare direttamente la funzione risultante è OK. Ma chiamando la composizione tramite call introduce un numero infinito di errori di compilazione (yaaay, nuovo record!):

assert(call(stringNotEmpty,testInput1) == true); // line 44 
assert(call(stringNotEmpty,testInput2) == false); 

L'uscita di compilazione (gcc 4.7, uscita completa vedi link Ideone qui sotto):

prog.cpp:16:9: error: ‘std::function<bool(int)> CompFn<std::function<int(std::basic_string<char>)>, std::function<bool(int)> >::b’ is private 
prog.cpp:44:5: error: within this context 
prog.cpp:15:9: error: ‘std::function<int(std::basic_string<char>)> CompFn<std::function<int(std::basic_string<char>)>, std::function<bool(int)> >::a’ is private 
prog.cpp:44:5: error: within this context 
prog.cpp:22:10: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) substituting ‘template<class Fn, class Arg> decltype (f(arg)) call(const Fn&, const Arg&) [with Fn = std::function<int(std::basic_string<char>)>; Arg = std::basic_string<char>]’ 
prog.cpp:22:10: required by substitution of ‘template<class Arg> decltype (call(((const CompFn*)this)->CompFn<Fn1, Fn2>::b, call(((const CompFn*)this)->CompFn<Fn1, Fn2>::a, arg))) CompFn::operator()(const Arg&) const [with Arg = Arg; Fn1 = std::function<int(std::basic_string<char>)>; Fn2 = std::function<bool(int)>] [with Arg = std::basic_string<char>]’ 
prog.cpp:8:6: required by substitution of ‘template<class Fn, class Arg> decltype (f(arg)) call(const Fn&, const Arg&) [with Fn = std::function<int(std::basic_string<char>)>; Arg = std::basic_string<char>]’ 
prog.cpp:22:10: required by substitution of ‘template<class Arg> decltype (call(((const CompFn*)this)->CompFn<Fn1, Fn2>::b, call(((const CompFn*)this)->CompFn<Fn1, Fn2>::a, arg))) CompFn::operator()(const Arg&) const [with Arg = Arg; Fn1 = std::function<int(std::basic_string<char>)>; Fn2 = std::function<bool(int)>] [with Arg = std::basic_string<char>]’ 
prog.cpp:8:6: required by substitution of ‘template<class Fn, class Arg> decltype (f(arg)) call(const Fn&, const Arg&) [with Fn = std::function<int(std::basic_string<char>)>; Arg = std::basic_string<char>]’ 
prog.cpp:22:10: required by substitution of ‘template<class Arg> decltype (call(((const CompFn*)this)->CompFn<Fn1, Fn2>::b, call(((const CompFn*)this)->CompFn<Fn1, Fn2>::a, arg))) CompFn::operator()(const Arg&) const [with Arg = Arg; Fn1 = std::function<int(std::basic_string<char>)>; Fn2 = std::function<bool(int)>] [with Arg = std::basic_string<char>]’ 
prog.cpp:8:6: [ skipping 890 instantiation contexts ] 
[ ...continues endlessly... ] 

Quando si converte la composizione in un std::function, è anche perfettamente a posto. Ma questo non permetterà di usare funtori polimorfici con la mia funzione comp, almeno non vedo un'opzione.

One "fix" è quello di non utilizzare trailing tipo di ritorno con decltype per il Comp::operator(), ma fissa il tipo di ritorno a bool (specializzato per questo singolo scenario di test).

Tutti e quattro i casi di test menzionati riassunte:

  • Test1 - Chiama direttamente la composizione -> OK
  • Test2 - Chiamare la composizione utilizzando call-> Errore
  • Test3 - - Trasmetti la composizione a std :: function, quindi chiama usando call -> OK
  • Test4 - Chiama il comp osizione utilizzando call. Fixed tipo di ritorno di Comp::operator() a bool -> OK

Il mio obiettivo è quello di renderecall un wrapper "seemless" per chiamare qualsiasi tipo di funzione: Funtori, puntatori a funzione, puntatori a funzione membro, puntatori variabile membro, ecc., e anche una composizione utilizzando comp. Ho un sacco di sovraccarichi per loro, ma io non voglio introdurre un sovraccarico per Comp<Fn1,Fn2> dal Fn1 o Fn2 può ancora una volta essere un qualsiasi tipo di funzione, sembra essere un "problema ricorsivo".

+0

Cosa succede se è stata sostituita un'espressione di Fn1 per a, Fn2 per b? Non completamente incapsulato, ma ancora utilizzando il tipo di ritorno finale senza menzionare membri privati. –

+0

Aah, vuoi dire 'std :: declval ()' invece di 'a' nell'espressione del tipo di ritorno finale? Fammi provare questo. Ottima idea – leemes

+0

@ScottJones Sei il mio eroe del giorno. Si prega di postarlo come risposta e lo accetterò. Ottima cattura! Tuttavia, sembra essere un bug del compilatore, dal momento che decltype dovrebbe solo "calcolare" il tipo di un'espressione senza prendere in considerazione alcuni specificatori di accesso (immagino)). Clang sembra compilarlo, secondo la risposta di Xeo. – leemes

risposta

2

Provare a sostituire un'espressione di Fn1 per a, Fn2 per b per evitare di menzionare membri privati. Ho provato questo in VC++, ma ho ottenuto un errore diverso:

template<class Arg> inline 
auto operator()(const Arg & arg) const -> decltype(call(Fn1(), call(Fn2(), arg))) { 
    return call(b, call(a, arg)); 
} 
+0

Come già discusso nei commenti, questo ha corretto l'errore di compilazione. Il numero infinito di errori di follow-up non è stato ancora rimosso. Avevo bisogno di applicare la stessa correzione a 'call' che, come puoi vedere, usa anche il tipo di ritorno finale. Sostituendo 'f' con' std :: declval () 'però risolto. – leemes

+0

Ma dovresti scrivere 'std :: declval ()' invece di 'T()', poiché potrebbe non avere un costruttore predefinito. – leemes

+0

@leemes Giusto - Stavo solo hackerando un'espressione per rimuovere aeb –

6

Clang compila bene il tuo caso di test non funzionante, e non riesco a vedere alcun errore con esso, quindi penso che questo sia un bug GCC. Si prega di presentare una segnalazione di bug con una minima replica (non inclusa) se è possibile.

Nota: per call, c'è già qualcosa di simile è nella norma - INVOKE, che non è una macro, ma un concetto, per così dire. È utilizzato da std::bind, std::function e altre cose, una delle quali è std::reference_wrapper. Ciò significa che è possibile eseguire std::ref(fun)(args...) per ottenere lo stesso valore di call.

+0

Grazie mille per il suggerimento su "INVOKE". Farò un tentativo (posso rendere il mio 'call' solo un involucro attorno ad esso, suppongo). Dovrebbe rendere il mio codice più pulito. E grazie per aver provato Clang. – leemes

Problemi correlati