2015-11-11 13 views
5

Sto scrivendo una libreria di ordinamento con oggetti funzione fascicolatore. Una delle classi principali, sorter_facade, è destinata a fornire alcuni sovraccarichi di operator() al sorter, in base ai sovraccarichi già esistenti. Ecco un semplice esempio ridotta di un oggetto heap_sorter, implementando un heapsort:Visibilità delle funzioni membro in una classe CRTP

struct heap_sorter: 
    sorter_facade<heap_sorter> 
{ 
    using sorter_facade<heap_sorter>::operator(); 

    template<typename Iterator> 
    auto operator()(Iterator first, Iterator last) const 
     -> void 
    { 
     std::make_heap(first, last); 
     std::sort_heap(first, last); 
    } 
}; 

Uno degli obiettivi più semplici di sorter_facade è fornire un sovraccarico iterabile al selezionatore di operator() quando un sovraccarico prendendo una coppia di iteratori esiste già. Ecco una ridotta implementazione di sorter_facade, sufficiente per il problema in esame:

template<typename Sorter> 
struct sorter_facade 
{ 
    template<typename Iterable> 
    auto operator()(Iterable& iterable) const 
     -> std::enable_if_t< 
      not has_sort<Sorter, Iterable>, 
      decltype(std::declval<Sorter&>()(std::begin(iterable), std::end(iterable))) 
     > 
    { 
     return Sorter{}(std::begin(iterable), std::end(iterable)); 
    } 
}; 

In questa classe, has_sort è un tratto utilizzato per rilevare se una selezionatrice ha un sovraccarico operator() prendendo un Iterable&. Si è implementato utilizzando una versione arrotolato a mano del detection idiom:

template<typename Sorter, typename Iterable> 
using has_sort_t = std::result_of_t<Sorter(Iterable&)>; 

template<typename Sorter, typename Iterable> 
constexpr bool has_sort = std::experimental::is_detected_v<has_sort_t, Sorter, Iterable>; 

Ora, per il problema reale: le seguenti main lavori bene con g ++ 5.2:

int main() 
{ 
    std::vector<int> vec(3); 
    heap_sorter{}(vec); 
} 

Tuttavia, it fails with clang++ 3.7.0, con il seguente messaggio di errore:

main.cpp:87:5: error: no matching function for call to object of type 'heap_sorter' 
    heap_sorter{}(vec); 
    ^~~~~~~~~~~~~ 

/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with Iterable = std::vector<int, std::allocator<int> >] 
    using enable_if_t = typename enable_if<_Cond, _Tp>::type; 
             ^

main.cpp:75:10: note: candidate function template not viable: requires 2 arguments, but 1 was provided 
    auto operator()(Iterator first, Iterator last) const 
    ^

1 error generated. 

A quanto pare, nel valutare la std::enable_if_t, sembra considerare che Sorter ha già una operator() in grado di prendere una Iterable&, che probabilmente significa che clang ++ e g ++ non lo valuta la "stessa" Sorter quando si controlla l'esistenza del sovraccarico .

Per questo semplice esempio, la rimozione del std::enable_if_t rende tutto il lavoro fatto, ma la classe sorter_facade è in realtà molto più grande di quello e ho bisogno di risolvere i problemi di ambiguità con altri sovraccarichi di operator(), quindi basta la rimozione non è un soluzione.

Quindi ... che cosa causa l'errore? I compilatori dovrebbero accettare o rifiutare questo codice? Infine, esiste un modo standard compatibile per farlo funzionare con le ultime versioni di g ++ e clang ++?


EDIT: come nota a margine, sono riuscito a ottenere tutte le folle di lavorare sia con g ++ 5 e clang ++ 3.8 con l'aggiunta di un ulteriore livello di magia nera al punto I' Non ho idea del motivo per cui funziona ancora. Mentre di tutte le domande precedenti tengono, ecco la «soluzione» (usando C++ 17 std::void_t):

tempate<typename Sorter> 
struct wrapper: 
    Sorter 
{ 
#ifdef __clang__ 
    using Sorter::operator(); 

    template<typename Iterable> 
    auto operator()(Iterable& iterable) const 
     -> std::enable_if_t<false, std::void_t<Iterable>> 
    {} 
#endif 
}; 

template<typename Sorter> 
struct sorter_facade 
{ 
    template<typename Iterable> 
    auto operator()(Iterable& iterable) const 
     -> std::enable_if_t< 
      not has_sort<wrapper<Sorter>, Iterable>, 
      decltype(std::declval<Sorter&>()(std::begin(iterable), std::end(iterable))) 
     > 
    { 
     return Sorter{}(std::begin(iterable), std::end(iterable)); 
    } 
}; 

Credo che abusa diversi comportamenti specifici del compilatore sia g ++ e clang ++ e raggiunge qualcosa che wasn 't pensato di funzionare, ma ancora ... Sono stupito che funzioni, anche in tutto il mio progetto, che ha molte altre cose difficili da gestire ...

+0

Ci scusiamo per la mia mancanza di conoscenza, ma cosa significa "non has_sort " significa in un modello? Voglio dire, so cosa * non * significa in inglese e cosa è * has_sort *, ma è una dichiarazione legale come argomento modello? Non l'ho mai visto prima, sono curioso. – skypjack

+5

@skypjack In modo abbastanza interessante, ['not' è un sinonimo di'! 'In C++] (http://en.cppreference.com/w/cpp/language/operator_logical) – jaggedSpire

+0

@jaggedSpire :-D Era semplicemente questo? È passato tanto tempo dall'ultima volta che l'ho visto e ho persino dimenticato che esiste !! Peccato per me. :-) ... Ho pensato ad alcune parole chiave correlate solo ai template e ho completamente perso il punto. Grazie. Ho intenzione di cambiare lavoro adesso. – skypjack

risposta

1

Sono sicuro che questo è un bug in clang . Il tipo di ritorno di sorter_facade<Sorter>::operator() dipende dall'argomento modello Iterator. Tuttavia, il compilatore sembra decidere di SFINAE prima di conoscere gli argomenti.

Ma bug o no, è possibile aggirare il problema rinviando esplicitamente il calcolo del tipo restituito. Ecco una versione che non dipende dalla magia nera. Funziona con gcc-5.2 e clang-3.6:

template<typename Sorter, typename Iterable> 
struct sort_result 
{ 
    using type = decltype(
     std::declval<Sorter&>()(
     std::begin(std::declval<Iterable&>()), 
     std::end(std::declval<Iterable&>()))); 
}; 

template<typename Sorter, typename Deferred> 
using sort_result_t = typename sort_result<Sorter, Deferred>::type; 

template<typename Sorter> 
struct sorter_facade 
{ 
    template <typename Iterable> 
    auto operator()(Iterable& iterable) const 
     -> sort_result_t<Sorter, Iterable> 
    { 
     return Sorter{}(std::begin(iterable), std::end(iterable)); 
    } 
}; 

struct heap_sorter: 
    sorter_facade<heap_sorter> 
{ 
    using sorter_facade<heap_sorter>::operator(); 

    template<typename Iterator> 
    auto operator()(Iterator first, Iterator last) const 
     -> void 
    { 
     std::make_heap(first, last); 
     std::sort_heap(first, last); 
    } 
}; 

int main() 
{ 
     std::vector<int> vec(3); 
      heap_sorter{}(vec); 
} 

Il trucco è: Il compilatore non sa se si specializzano il result_type in seguito. Quindi deve aspettare fino a quando non lo usi effettivamente, prima di provare a determinare il tipo di ritorno.

Problemi correlati