2017-04-25 1 views
-1

Ist es möglich, eine Funktion in C++ zu überladen, die ähnliche, jedoch unterschiedliche Templatesignaturen aufweist?Ambiguous Function Overloads [z.B. max()]

Betrachten Sie die Funktion max(). Was ist, wenn ich eine max()-Funktion haben möchte, die ein Paar der folgenden drei Optionen aufnehmen kann: int, *int Bereich, int Iterator-Bereich.

Die STL hat diese Funktionalität aufgeteilt in zwei Funktionen:

So können Sie oder können Sie dieses Verhalten in C nicht erreichen ++ und wenn ja, wie?

Edit: dies würde max(vec.begin(), vec.end()); und max(vec[0], vec[1]); erfolgreich kompilieren und finden Sie die max des Bereichs und der max der beiden übergebenen Objekte. Ich persönlich denke, dass es nicht möglich ist, sonst würden die beiden Funktionen in der STL kombiniert werden, aber ich würde bestätigen, dass es nicht machbar ist.

+1

Ja, es ist möglich. Hast du es versucht? Wo sind Sie in Schwierigkeiten geraten? – aschepler

+0

Solange die Funktionsaufrufe nicht mit Funktionsaufrufen mit dem gleichen Namen interferieren, sollten Sie keine Probleme mit dem Überladen haben. –

+0

Sie können keine Funktion implementieren, die folgendermaßen aussieht: int max (const int * p); es gibt keine Möglichkeit zu sagen, wie viele Ints 'p' auf etwas verweisen, also würden Sie etwas wie 'int max (const int * begin, const int * end);' und ähnlich für Iteratoren haben wollen. –

Antwort

1

Im Moment sind die Überladungen mehrdeutig, weil Sie sie nicht eingeschränkt haben. Wie soll der Compiler wissen, dass der erste nicht für Iteratoren verwendet werden sollte?

Die Antwort ist einfach, Sie müssen es sagen. Zum Glück definiert der Standard std::iterator_traits, die Sie für alle Arten von Informationen über den Iterator-Typ abfragen können, den Sie ihm übergeben haben - solange es ein Iterator ist.

Der letzte Punkt hier ist der Schlüssel: Wenn std::iterator_tags<T> keine verschachtelte typedef iterator_category hat, dann ist T kein Iterator. Daher können wir member detection using void_t verwenden, um eine benutzerdefinierte Eigenschaft zu definieren, die überprüft, ob ein bestimmter Typ ein Iterator ist.

Im Code sieht es wie folgt aus:

// C++17 void_t 
template <typename...> 
using void_t = void; 

template <typename T, typename = void> 
struct is_iterator : std::false_type { 
}; 

template <typename T> 
struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>> : std::true_type { 
}; 

template <typename T> 
constexpr auto is_iterator_v = is_iterator<T>::value; 

Hier habe ich auch die constexpr für Bequemlichkeit variable is_iterator_v definiert.

Wir können diese Eigenschaft in Kombination mit std::enable_if verwenden, um selektiv eine der beiden zuvor mehrdeutigen Überladungen basierend darauf, ob ein Typ T ein Iterator ist oder nicht, zu deaktivieren. Um die Template-Deklaration template <typename T> einfach durch template <typename T, std::enable_if_t<is_iterator_v<T>, int> = 0> bzw. template <typename T, std::enable_if<!is_iterator_v<T>, int> = 0> zu ersetzen.

Putting alles zusammen, sollte der Code wie folgt aussehen dies:

// C++17 void_t 
template <typename...> 
using void_t = void; 

template <typename T, typename = void> 
struct is_iterator : std::false_type { 
}; 

template <typename T> 
struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>> : std::true_type { 
}; 

template <typename T> 
constexpr auto is_iterator_v = is_iterator<T>::value; 

template<typename T, std::enable_if_t<!is_iterator_v<T>, int> = 0> 
constexpr T maximum(T a, T b) 
{ 
    return (a > b) ? a : b; 
} 

template<typename Iter, std::enable_if_t<is_iterator_v<Iter>, int> = 0> 
constexpr Iter maximum(Iter begin, Iter end) 
{ 
    if(begin == end) { 
     return end; 
    } 
    auto max = begin; 
    for (; begin != end; ++begin) { 
     if(*begin > *max) { 
      max = begin; 
     } 
    } 
    return max; 
} 

Sie können auch das Arbeitsbeispiel auf wandbox finden.

Verwandte Themen