2009-08-26 18 views
15

Mam następujący problem:priorytet przy wyborze przeciążone funkcje szablonów w C++

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    static const char *func(Base *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 
}; 

Gdybym teraz zrobić

Derived derived; 
Different different; 
std::cout << "Derived: " << X::func(&derived) << std::endl; 
std::cout << "Different: " << X::func(&different) << std::endl; 

uzyskać

Derived: Generic 
Different: Generic 

Ale co chcę jest, że dla wszystkich klas wyprowadzonych z Bazy wywoływana jest określona metoda. więc wynik powinien być:

Derived: Specific 
Different: Generic 

Czy jest jakiś sposób mogę przeprojektować X: func (...) s, aby osiągnąć ten cel?

EDIT:

Załóżmy, że nie jest znany przez dzwoniącego X :: func (...) jeśli klasa przedstawiła jako parametr pochodzi z bazy, czy nie. Więc Casting to Base nie jest opcją. W rzeczywistości ideą leżącą u podstaw tego wszystkiego jest to, że X :: func (...) powinien "wykryć", jeśli parametr wywodzi się z bazy i nie wywoływać innego kodu. A ze względów wydajnościowych "wykrywanie" powinno odbywać się w czasie kompilacji.

Odpowiedz

14

Znalazłem BARDZO proste rozwiązanie!

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
private: 
    template <typename T> 
    static const char *intFunc(const void *, T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    template <typename T> 
    static const char *intFunc(const Base *, T *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    return intFunc(data, data); 
    } 
}; 

Działa to świetnie i jest bardzo cienka! Sztuką jest pozwolić kompilatorowi wybrać poprawną metodę przez (w przeciwnym razie bezużyteczny) pierwszy parametr.

+1

+1, powinienem był o tym pomyśleć. Ta sztuczka jest faktycznie używana w STL do wybierania między różnymi wersjami algorytmów opartych na kategorii przekazywanych iteratorów. – avakar

+1

Pamiętaj o wersji "Zwiększ", ale będzie działać z innymi cechami niż wymienialność. – avakar

+0

dzięki za ogłoszenie, to naprawdę dobre rozwiązanie. – ttvd

0

Wystarczy typecast pochodzi oprzeć

X :: func ((podstawowa *) & pochodzi)

to działa ....

+3

W C++ lepiej jest użyć static_cast lub dynamic_cast do rzutowania w stylu C. (c.f. Stevens) –

1

wyrażenie:

X::func(derived) 

Środki kompilator wygeneruje deklarację i kod, który faktycznie ma tę sygnaturę:

static const char *func(Derived *data); 

który okazuje się być lepszy mecz niż:

static const char *func(Base *data); 

Funkcja szablon będzie używany do wszystkiego, co jest legalne, na przykład dla TypeName dowolna klasa, której używasz jako T, która skutecznie uniemożliwi użycie wersji twojej funkcji, ze względu na zasady kompilacji czasu.

Moja sugestia jest użycie specialization w X dla konkretnych typów, tj .:

template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

template <> 
    static const char *func(Derived *data) // 'Derived' is the specific type 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

nadzieję, że działa!

+0

Nie, to nie działa. – avakar

+0

Nie, to nie działa. Po prostu wypróbowałem to z gcc. Ma to sens, ponieważ wciąż lepiej pasuje do ogólnej wersji. – Troubadour

+0

Whoops - ma na celu specjalizację jako static const char * func (Derived * data) ... te dwa powinny być przynajmniej równe w oczach kompilatora. Mam nadzieję, że wybrał ten wyraźnie wyspecjalizowany ... – jscharf

7

Do tego celu należy użyć SFINAE. W poniższym kodzie pierwsza funkcja może być utworzona tylko wtedy, gdy przekazujesz coś, czego nie można (niejawnie) przekonwertować na Base *. Druga funkcja ma to odwrócić.

Możesz przeczytać na stronie enable_if.

#include <iostream> 
#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits.hpp> 

class Base {}; 
class Derived : public Base {}; 
class Different {}; 

struct X 
{ 
    template <typename T> 
    static typename boost::disable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Generic"; 
    } 

    template <typename T> 
    static typename boost::enable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Specific"; 
    } 
}; 

int main() 
{ 
    Derived derived; 
    Different different; 
    std::cout << "Derived: " << X::func(&derived) << std::endl; 
    std::cout << "Different: " << X::func(&different) << std::endl; 
} 
+0

Cóż, 'enable_if' jest dość łatwe do wdrożenia. Jednak "is_convertible" wydaje się być zupełnie inną historią. Osobiście nie chciałbym go wdrożyć na własną rękę. – avakar

+0

Dokładnie. Wiele z nich wydaje się jednak obejściem dla różnych niezgodnych kompilatorów. Być może gdzieś tam ukryta jest krótka elegancka wersja. Naprawdę nie mam ochoty go szukać, a ja też nie zakładam. Zrobiłbym z Boost :-) – avakar

+0

Należy jednak pamiętać, że mogą istnieć dwie pojęcia: przeliczalność wskaźnika i uniwersalna wymienialność.Poniższa analiza testu wymienialności wskaźnika: Zasadniczo jest to: 'template struct is_ptr_convertible {static char (& is (B *)) [1]; static char (& is (...)) [2]; statyczna wartość stałej bool = (sizeof is ((D *) 0) == 1); }; ' –

1

Jeśli używasz impuls, można zrobić to z jakiegoś szablonu metaprogramowanie:

#include <boost/type_traits/is_base_of.hpp> 

class X 
{ 
private: 
    template <typename T> 
    static const char *generic_func(T *data) 
    { 
     // Do something generic... 
     return "Generic"; 
    } 

    template <typename T> 
    static const char *base_func(T *data) 
    { 
     // Do something specific... 
     return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char* func(T* data) 
    { 
     if (boost::is_base_of<Base, T>::value) 
      return base_func(data); 

     return generic_func(data); 
    } 
}; 

is_base_of metafunkcji jest uwzględniany w czasie kompilacji i optymalizator najprawdopodobniej usunięcia martwej gałęzi if w funkcji func. Takie podejście pozwala mieć więcej niż jeden konkretny przypadek.

0

Szukałem ustawienia priorytetów na nakładających się włączeniach enable_if, w szczególności dla odrzucenia wywoływania metod kontenera STL, gdzie moje cechy były takie, jak is_assignable is_insterable itd., Z którymi występuje nakładanie się na wiele kontenerów.

Chciałem nadać priorytet przypisaniu, jeśli istniało, w innym przypadku użyj iteratora wstawiania. Jest to ogólny przykład tego, co wymyśliłem (zmodyfikowany nieskończonymi poziomami priorytetu przez niektórych przydatnych ludzi w kanale #boost irc). Działa tak, jak niejawna konwersja poziomu priorytetu plasuje przeciążenie poniżej innego, które w przeciwnym razie jest równie ważną opcją - usuwając niejednoznaczność.

#include <iostream> 
#include <string> 

template <std::size_t N> 
struct priority : priority<N - 1> {}; 

template <> 
struct priority<0> {}; 

using priority_tag = priority<2>; 

template <typename T> 
void somefunc(T x, priority<0>) 
{ 
    std::cout << "Any" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_pod<T>::value > 
somefunc(T x, priority<2>) 
{ 
    std::cout << "is_pod" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_floating_point<T>::value > 
somefunc(T x, priority<1>) 
{ 
    std::cout << "is_float" << std::endl; 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x, priority_tag{}); 
    int y = 1; 
    somefunc(y, priority_tag{}); 
    std::string z; 
    somefunc(z, priority_tag{}); 
    return 0; 
} 

Został również zasugerował, że w C++ 14 może po prostu użyć constexpr if aby osiągnąć to samo, co było znacznie czystsze jeśli Visual Studio 2015 wspiera je. Mam nadzieję, że to pomoże komuś innemu.

#include <iostream> 
#include <string> 

template <typename T> 
void somefunc(T x) 
{ 
    if constexpr(std::is_floating_point<T>::value) { 
     static_assert(std::is_floating_point<T>::value); 
     std::cout << "is_float" << std::endl; 
    } else if constexpr(std::is_pod<T>::value) { 
     static_assert(std::is_pod<T>::value); 
     std::cout << "is_pod" << std::endl; 
    } else { 
     static_assert(!std::is_floating_point<T>::value); 
     static_assert(!std::is_pod<T>::value); 
     std::cout << "Any" << std::endl; 
    } 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x); 
    int y = 1; 
    somefunc(y); 
    std::string z; 
    somefunc(z); 
    return 0; 
} 

// dzięki k-ballo @ #boost!

Powiązane problemy