Frage Ist es falsch, Templates mit der impliziten Annahme zu verwenden, dass bestimmte Member-Funktionen des parametrisierten Typs definiert werden?


Sag, du schreibst eine wirklich schlechte Klasse

template <typename T>
class IntFoo
{
  T container ;
public:
  void add( int val )
  {
    // made an assumption that
    // T will have a method ".push_front".
    container.push_front( val ) ;
  }
} ;

Ignoriere die Tatsache, dass die Klasse annimmt, dass der Container sein wird something<int>Beachten Sie stattdessen, dass

IntFoo< list<int> > listfoo ;
listfoo.add( 500 ) ; // works

IntFoo< vector<int> > intfoo;
//intfoo.add( 500 ) ; // breaks, _but only if this method is called_..

Im Algemeinen, ist es in Ordnung, eine Memberfunktion eines parametrisierten Typs wie diesem aufzurufen? Ist das schlechtes Design? Hat dieses (Anti-) Muster einen Namen?


5
2017-10-03 18:28


Ursprung


Antworten:


Das ist vollkommen in Ordnung und wird als Compile-Time Duck Typing bezeichnet und an allen möglichen Orten in der Standardbibliothek selbst eingesetzt. Und im Ernst, wie würden Sie etwas Nützliches mit einer Vorlage tun, ohne das Template-Argument zu übernehmen, um bestimmte Funktionalitäten zu unterstützen?

Werfen wir einen Blick darauf irgendein Algorithmus in der stdlib, z.B. std::copy:

template<class InIt, class OutIt>
OutIt copy(InIt first, Init last, OutIt out){
  for(; first != last; ++first)
    *out++ = *first;
  return out;
}

Hier ein Objekt vom Typ InIt wird angenommen, um zu unterstützen operator*() (für Indirektion) und operator++() um den Iterator voranzutreiben. Für ein Objekt vom Typ OutItEs wird angenommen, dass es unterstützt operator*() auch, und operator++(int). Eine allgemeine Annahme ist auch, dass alles zurückkommt *out++ ist zuweisbar (aka Cabrio) von was auch immer *first Erträge. Eine andere Annahme wäre, dass beide InIt und OutIt sind Kopie konstruierbar.

Ein anderer Ort, an dem dies verwendet wird, ist in jedem Standardcontainer. In C ++ 11, wenn Sie verwenden std::vector<T>, T muss kopierbar sein dann und nur dann, wenn Sie verwenden jede Mitgliedsfunktion, die eine Kopie erfordert.

All dies macht es möglich, dass benutzerdefinierte Typen genauso behandelt werden wie ein eingebauter Typ, d. H. Sie sind First-Class-Bürger der Sprache. Sehen wir uns noch einmal einige Algorithmen an, nämlich solche, die einen Callback verwenden, der auf einen Bereich angewendet werden soll:

template<class InIt, class UnaryFunction>
InIt for_each(InIt first, InIt last, UnaryFunction f){
  for(; first != last; ++first)
    f(*first);
  return first;
}

InIt Es wird angenommen, dass die gleichen Operationen wieder wie in der copy Beispiel oben. Aber jetzt haben wir es auch UnaryFunction. Es wird davon ausgegangen, dass Objekte dieses Typs die Schreibnotation der Postfix-Funktion unterstützen, insbesondere mit ein Argument (unär). Weiter wird angenommen, dass dies der Parameter dieses Funktionsaufrufs von was auch immer konvertierbar ist *first Erträge.

Das typische Beispiel für die Verwendung dieses Algorithmus ist eine einfache Funktion:

void print(int i){ std::cout << i << " "; }

int main(){
  std::vector<int> v(5); // 5 ints
  for(unsigned i=0; i < v.size(); ++i)
    v[i] = i;
  std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}

Sie können jedoch auch verwenden Funktionsobjekte dafür - ein benutzerdefinierter Typ, der überlädt operator():

template<class T>
struct generate_from{
  generate_from(T first) : _acc(first) {}
  T _acc;
  void operator()(T& val){ val = _acc++; }
};

int main(){
  std::vector<int> v(5); // 5 ints
  // yes, there is std::iota. shush, you.
  std::for_each(v.begin(), v.end(), generate_from<int>(0)); // fills 'v' with [0..4]
  std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}

Wie Sie sehen können, ist mein benutzerdefinierter Typ generate_from Kann genau wie eine Funktion behandelt werden, es kann aufgerufen werden als ob es eine Funktion wäre. Beachten Sie, dass ich einige Annahmen anstelle T im generate_from, nämlich muss es sein:

  • kopierfähig (im ctor)
  • post-inkrementell (in der operator())
  • kopierbar (in der operator())

12
2017-10-03 18:38