Nur einen template Typen spezifizieren bei 3 template Typen
-
Hallo,
ich habe ein Funktionstemplate der Form
template<typename Iterator, typename ValueType = typename std::iterator_traits<Iterator>::value_type, typename Operation = std::plus<ValueType>> static ValueType average( Iterator begin, Iterator end, Operation op ) { if( begin == end ) return ValueType(); else return std::accumulate( begin, end, ValueType(), op ) / static_cast<ValueType>( std::distance( begin, end ) ); }
Soweit, so gut. Jetzt möchte ich z.B. den Durchschnitt für einen Vektor aus int Werten nicht als int haben, sondern als double. Dazu müsste ich eigentlich nur den
ValueType
spezifizieren, die übrigen Type lassen sich ja aus den Aufrufparametern herleiten, sodass der Aufruf so aussehen könnte:std::vector<int> v( ... ); double avg = average<double>( v.begin(), v.end() );
Nur weiß ich nicht, wie ich dem Compiler das jetzt erklären soll, dass sich die explizite Typspezifikation auf den 2. Typ bezieht und die beiden anderen Typen aus den Aufrufparametern hergeleitet werden können. Ansonsten müsste ich auch den Iteratortypen spezifizieren, was die Benutzung der Funktion etwas umständlich macht. Den Eregbnistypen kann ich nicht nach vorne ziehen, da er ohne Vorgabe vom Iteratortyp abhängt.
Wie löst man das? Ich könnte (wie beistd::accumulate
) einen Initialwert übergeben, aber das finde ich auch nicht schön, da er eigentlich überflüssig ist.
-
z.B. so
struct use_traits_type_tag {}; template < typename ValueType = use_traits_type_tag, typename Iterator, typename Operation = std::plus<typename std::conditional<std::is_same<ValueType, use_traits_type_tag>{}, typename std::iterator_traits<Iterator>::value_type, ValueType>::type> auto average( Iterator begin, Iterator end, Operation op = Operation{}) { using value_type = typename std::conditional<std::is_same<ValueType, use_traits_type_tag>{}, typename std::iterator_traits<Iterator>::value_type, ValueType>::type; if( begin == end ) return value_type(); else return std::accumulate( begin, end, value_type(), op ) / static_cast<value_type>( std::distance( begin, end ) ); }
Eleganter wäre evtl. so etwas:
template < typename Iterator, typename Operation = std::plus<typename std::iterator_traits<Iterator>::value_type> auto average( Iterator begin, Iterator end, Operation op = Operation{}) { using value_type = typename std::iterator_traits<Iterator>::value_type; using op_type = decltype( op( value_type{}, value_type{} ) ); if( begin == end ) return op_type(); else return std::accumulate( begin, end, op_type(), op ) / static_cast<op_type>( std::distance( begin, end ) ); }
mit Aufruf
average( begin, end, std::plus<double>{} );
o.ä.
Alles ungetestet.
-
Danke camper,
Beispiel 1 verstehe ich nicht, das muss ich mir mal in Ruhe angucken. Beispiel 2 funktioniert nicht, da bei
op
die beiden Parameter nicht zwingend den gleichen Typ haben. In einem konkreten Fall sieht das bei mir so aus:struct ParcelDataItem { std::time_t Timestamp; double Value; ... }; double f( const std::vector<ParcelDataItem>& vec ) { return average( vec.begin(), vec.end(), []( double Sum, const ParcelDataItem& Item ) { return Sum + Item.Value; } ); }
Da dürfte das
using op_type = decltype( op( value_type{}, value_type{} ) );
nicht funktionieren. Konnte das alles bisher aber auch noch nicht testen. Ich habe das jetzt erst ein Mal mit einem explizit übergebenen Initialwert gelöst, auch wenn´s nicht schön ist.