2016-08-01 5 views
3

Ich möchte eine Vorlagefunktion in C++ 11 implementieren, die ein Paar Iteratoren benötigt. Die Implementierung sollte eine spezielle Verarbeitung durchführen, wenn ein Paar von Iteratoren übergeben wird, deren Werttyp ein std::pair beliebiger Typen ist. Ich habe versucht, mit den folgenden Definitionen zu kommen:Wie Funktion für verschiedene Iterator value_types in C++ zu überladen

// arbitrary value types 
template<typename Iter> 
void process(Iter begin, Iter end) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << *iter << "\n"; 
    } 
} 

// std::pair value types 
template<typename Iter, typename First, typename Second, 
    typename std::enable_if< 
     std::is_same< 
      typename std::iterator_traits<Iter>::value_type, std::pair<First,Second> 
      >::value 
     >::type* = 0> 
void process(Iter begin, Iter end) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << (*iter).first << " " << (*iter).second << "\n"; 
    } 
} 

Mit dem folgenden Beispielcode:

std::vector<int> int_vec{{1,2,3,4}}; 
process(int_vec.begin(), int_vec.end()); 

richtig ruft die erste Definition der Funktion process.

jedoch
std::vector<std::pair<int,std::string>> pair_vec{ 
    {std::make_pair(1, "First"), std::make_pair(2, "Second")}}; 
process(pair_vec.begin(), pair_vec.end()); 

nennen auch die erste Definition und die Ergebnisse in der Fehlermeldung (mit Clang):

error: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>') and 'std::__1::pair<int, std::__1::basic_string<char> >') 

Warum nicht der Compiler die zweite Definition in diesem Fall abholen? Wie muss ich die überladene Funktion ändern?

Antwort

1

Das Problem ist die Vorlage Argumente First und Second nicht von Iter, das einzige Argument Vorlage Argument Abzug unterliegt abgeleitet werden, an dem Punkt, an dem Sie sie in der Funktion Signatur erfordern. mit einer Helfer-Klasse (im Beispiel unten IsPair)

Sie können jedoch verwenden IterFirst und Second abzuleiten, und Template-Spezialisierung verwendet es zwischen zwei Optionen auszuwählen.

Beispiel:

template<typename T> 
struct IsPair 
{ 
    static const bool value = false; 
}; 

template<typename First, typename Second> 
struct IsPair<std::pair<First, Second>> 
{ 
    static const bool value = true; 
}; 

// arbitrary value types 
template<typename Iter> 
void process(Iter begin, Iter end, 
      typename std::enable_if< 
       !IsPair<typename std::iterator_traits<Iter>::value_type>::value 
      >::type* = 0) 
{ 
    for (Iter iter = begin; iter != end; ++iter) 
    { 
     std::cout << *iter << "\n"; 
    } 
} 

// std::pair value types 
template<typename Iter> 
void process(Iter begin, Iter end, 
      typename std::enable_if< 
       IsPair<typename std::iterator_traits<Iter>::value_type>::value 
      >::type* = 0) 
{ 
    for (Iter iter = begin; iter != end; ++iter) 
    { 
     std::cout << (*iter).first << " " << (*iter).second << "\n"; 
    } 
} 

Working example on coliru

1

Warum nicht Tag verwenden zB Disposition:

#include <iostream> 
#include <vector> 
#include <utility> 

template <class> struct tag { }; 

template <class Iter, class T> 
void process_impl(Iter begin, Iter end, tag<T>) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << *iter << "\n"; 
    } 
} 

template <class Iter, class First, class Second> 
void process_impl(Iter begin, Iter end, tag<std::pair<First, Second>>) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << (*iter).first << " " << (*iter).second << "\n"; 
    } 
} 

template <class Iter> 
void process(Iter begin, Iter end) { 
    process_impl(begin, end, tag<typename Iter::value_type>{}); 
} 

int main() { 
    std::vector<int> int_vec{{1,2,3,4}}; 
    process(int_vec.begin(), int_vec.end()); 
    std::vector<std::pair<int,std::string>> pair_vec{{std::make_pair(1, "First"), std::make_pair(2, "Second")}}; 
    process(pair_vec.begin(), pair_vec.end()); 
} 
3

In Ihrem Versuch enable_if zu verwenden, werden Sie zwei Template-Parameter einzuführen, die sind nicht-abgeleitete Kontexte. Es gibt keine Möglichkeit für den Compiler zu bestimmen, was First und Second sind, so dass die Überladung immer aus dem Überlastungssatz entfernt wird. Es wird nie in Betracht gezogen.

Ich würde vorschlagen, SFINAE vollständig zu umgehen. Wenn das, was du bist einfach tun, beinhaltet den Bereich einige aktuelle für jedes Element durchgeführt wird, nur Funktion Überlastungen verwenden:

template <class T> 
void process_impl(T const& elem) { 
    // generic case 
    std::cout << elem << '\n'; 
} 

template <class T, class U> 
void process_impl(std::pair<T, U> const& elem) { 
    // overload for pair 
    std::cout << elem.first << ' ' << elem.second << '\n'; 
} 

template <class Iter> 
void process(Iter first, Iter last) { 
    // if C++14 
    std::for_each(first, last, [](auto&& elem){ process_impl(elem); }); 

    // if C++11 
    using E = typename std::iterator_traits<Iter>::reference; 
    std::for_each(first, last, [](E elem) { process_impl(elem); }); 
} 
1

Die zweite Spezialisierung nie, da der Compiler nie in der Lage genannt wird, ist die First und Second Vorlagentyp Argumente abzuleiten . Was Sie stattdessen benötigen, ist eine Eigenschaft, die prüft, ob ein bestimmter Typ eine Spezialisierung von ist. Hier ist, was Sie tun können:

template <template <typename ...> class Ref, typename T> 
struct is_template_specialization : std::false_type {}; 

template <template <typename ...> class Ref, typename ... Args> 
struct is_template_specialization<Ref, Ref<Args...>> : std::true_type {}; 

template <typename T> 
using is_pair = is_template_specialization<std::pair, T>; 

nun mit einer solchen Eigenschaft, Ihre SFINAE Zustand wird:

// std::pair value types 
template<typename Iter, 
    typename std::enable_if< 
     is_pair<typename std::iterator_traits<Iter>::value_type 
      >::value 
     >::type* = 0> 
void process(Iter begin, Iter end) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << (*iter).first << " " << (*iter).second << "\n"; 
    } 
} 

Ein Problem ergibt sich: wenn es sich tatsächlich um ein Paar, das Sie verwenden. Sie haben zwei verfügbare Überladungen ...Der Anruf wird nicht eindeutig, so benötigen Sie einen SFINAE Zustand über die gemeinsame Umsetzung hinzuzufügen:

// arbitrary value types 
template<typename Iter, 
    typename std::enable_if< 
     ! is_pair<typename std::iterator_traits<Iter>::value_type>::value 
    >::type* = nullptr> 
void process(Iter begin, Iter end) { 
    for (Iter iter = begin; iter != end; ++iter) { 
     std::cout << *iter << "\n"; 
    } 
} 

Dadurch wird sichergestellt, dass nur eine Implementierung für einen bestimmten Anruf zur Verfügung steht als auf diesen live demo

demonstriert