2016-06-16 22 views
2

In Python können wir dies tun:Ist es möglich, in C++ eine templated, Single-Parameter-Summenfunktion zu haben?

int_list = [1, 2, 3, 4, 5] 
print(sum(int_list)) # prints 15 
float_tuple = (1.2, 3.4, 9.9) 
print(sum(float_tuple)) # prints 14.5 

Die sum Funktion nimmt jede iterable von Elementen, die wissen, wie man miteinander und mit 0 und erzeugt die Summe hinzugefügt werden.

Ich wollte eine identische Funktion in C++ 11 machen. Ich bin mir bewusst, dass es die accumulate Methode gibt, aber ich möchte eine Funktion, die einen einzelnen Parameter akzeptiert. Im Grunde möchte ich wissen, wie die folgende Code Kompilierung zu machen:

#include <string> 
#include <iostream> 
#include <vector> 
#include <deque> 
#include <list> 

template<typename iterable> 
auto sum(iterable iterable_) { 
    auto it = iterable_.begin(); 
    auto end = iterable_.end(); 
    if (it == end) { 
     return 0; 
    } 
    auto res = *(it++); 
    while (it != end) { 
     res += *it++; 
    } 
    return res; 
} 

int main() { 
    std::vector<int> int_vector = {0, 1, 2, 3, 4, 5}; 
    std::cout << sum(int_vector) << '\n'; // prints 15 
    std::deque<int> int_deque = {4, 5, 7}; 
    std::cout << sum(int_deque) << '\n';  // prints 16 
    std::list<float> float_list = {1.2, 3.4, 9.9}; 
    std::cout << sum(float_list) << '\n'; // should print 14.5, but produces error. 

} 

Dieser Code fast funktioniert. Das Problem ist, dass auto die return 0; in dem Fall sieht, dass das iterable leer ist und es annimmt, dass die Funktion int zurückgeben muss. Dann sieht es, dass die float Version eine float zurückgibt und es wird verwirrt. Gibt es eine Möglichkeit, den Compiler zu sagen, sagen return float(0), wenn es sieht, dass die return später zurück float?

+0

Ersetzen Sie 'return 0' durch' return {} '? –

+0

Neben: 'sum' nimmt eine * Kopie * nach Wert des iterablen statt eine Referenz darauf. – kfsone

+0

@kfsone, war mir nicht bewusst. Ich dachte, Python-Objekte (zumindest in CPython) wurden als PyObject * übergeben? Du meinst, dass die gesamte Liste für die Summenfunktion in den Speicher kopiert wird? Das scheint schrecklich ineffizient zu sein. –

Antwort

5

Ja, Sie können dies zumindest für Standard-Container funktionieren lassen.

Ein Standardcontainer definiert einen Typalias mit dem Namen value_type für den Werttyp, der in diesem Container gespeichert ist. Für einen leeren Behälter, einen Wert konstruierten Objekt dieses Typs zurückgeben kann:

template<typename iterable> 
auto sum(iterable const &iterable_) { 
    auto it = iterable_.begin(); 
    auto end = iterable_.end(); 
    if (it == end) { 
     return typename iterable::value_type(); 
    } 
    auto res = *(it++); 
    while (it != end) { 
     res += *it++; 
    } 
    return res; 
} 

default-konstruierbar Dies ist unabhängig von der enthaltenen Typ ist, aber das ist wahrscheinlich kein großes Problem (funktioniert natürlich für primitive Typen wie int und float).

+0

Das funktioniert! Es macht Sinn, dass der Container weiß, welche Art von Objekt er speichert.Ich bin froh, dass das dem Benutzer ausgesetzt ist, damit wir es ausnutzen können! –

+3

Um Raw-Arrays zu unterstützen, können Sie die freistehenden 'std :: begin'- und' std :: end'- und 'decltype'-Befehle anstelle von 'iterable :: value_type()' verwenden (was für ein RAW-Array nicht existiert). Außerdem würde ich den Rückgabetyp direkt deklarieren, indem ich 'declltype' verwende. Dann würde 'return 0;' genügen. :) –

2

Der folgende Ansatz funktioniert auch mit nicht standardmäßigen Containern; solange etwas implementiert begin() und end(), in einer sinnvollen Weise.

#include <list> 
#include <iostream> 
#include <type_traits> 

template<typename iterable> 
auto sum(iterable && iterable_) { 
    auto it = iterable_.begin(); 
    auto end = iterable_.end(); 

    typedef typename std::remove_reference<decltype(*it)>::type value_type; 

    if (it == end) { 
     return value_type(0); 
    } 
    auto res = *(it++); 
    while (it != end) { 
     res += *it++; 
    } 
    return res; 
} 

int main() { 
    std::list<float> float_list = {1.2, 3.4, 9.9}; 
    std::cout << sum(float_list) << '\n'; // works now. 
} 
+2

Mit 'std :: begin()' (oder 'cbegin') anstelle von' .begin() ', sollte dies auch auf Arrays funktionieren, weil' typedef' eingeführt wurde. –

5

Wenn Sie etwas mit jeden C++ 11 Bereich arbeiten (das heißt, alles, was Sie in einem Bereich Basis für die Expression iterieren können - einschließlich Arrays sowie Behälter, die frei begin haben und end), können wir nur einige using-Deklarationen hinzufügen und std::accumulate

template <class Range> 
auto sum(Range&& range) { 
    using std::begin; 
    using std::end; 
    using T = std::decay_t<decltype(*begin(range))>; 

    return std::accumulate(begin(range), end(range), T{}); 
} 

wickeln Wenn Sie nicht wollen, accumulate wickeln, dann können Sie einfach neu implementieren, dass die Schleife als auch das gleiche zu tun.