2016-03-11 10 views
6

TL; DR Ich mag eine Template-Funktion Process(T value) schreiben, die unterschiedlich für unterschiedliche Werte verhält sich in Abhängigkeit von der Existenz einer nicht-Elementfunktion CreateProcessor<T>(). Was kann ich dafür tun?SFINAE zum Erfassen Existenz Drittschablonenfunktion

Ich habe ein Problem mit SFINAE. Angenommen, wir müssen die Funktion CreateProcessor unterstützen, die eine Implementierung der Schnittstelle IProcessor<T> für einen Typ vom Typ T zurückgibt.

In C++ können wir nicht mehrere Überladungen einer Funktion erzeugen, die sich nur im Rückgabetyp unterscheiden, also müssen wir die Funktion CreateProcessor auch als Template-Funktion parametrieren, die durch T parametrisiert wird.

Nehmen wir nun an, dass wir eine Template-Funktion Process<T>(T value) schreiben wollen, die auf Existenz CreateProcessor<T>() je funktioniert anders, nämlich sollte es value unter Verwendung des Prozessors im Falle verarbeiten CreateProcessor<T>() implementiert ist, sonst ist es zu Fehlern führen sollte.

ich versucht, den folgenden Code zu schreiben:

#include <cstdio> 
#include <type_traits> 

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t. 
template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

// An interface for a processor that receives a value of specific type. 
template<class T> 
class IProcessor { 
public: 
    virtual void process(T value) = 0; 
}; 

// A processor for int. 
class IntProcessor : public IProcessor<int> { 
public: 
    virtual void process(int value) override { 
     printf("IntProcessor::process is called for value = %d\n", value); 
    } 
}; 

// Template prototype. 
template<class T> 
IProcessor<T>* CreateProcessor(); 

// Template specialization for int. 
template<> 
IProcessor<int>* CreateProcessor() { 
    return new IntProcessor(); 
} 

// Detector of CreateProcessor. 
template<class, class=void> 
struct CreateProcessorImplemented : std::false_type { }; 

template<class T> 
struct CreateProcessorImplemented<T, void_t<decltype(CreateProcessor<T>())>> : std::true_type { }; 


// Specializations depending on existence of CreateProcessor. 
template <typename T> 
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    IProcessor<T>* processor = CreateProcessor<T>(); 
    processor->process(value); 
} 

template <typename T> 
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    printf("Processor for requested typename is unavailable\n"); 
} 


int main() { 
    Process(42); 
    Process("abc"); 

// static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
/* This static_assert fails with an error: 
* code.cpp:56:5: error: static assertion failed: :(
*  static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
*/ 
} 

Obwohl dies in Verknüpfung Fehler führt:

/tmp/ccTQRc9N.o:code.cpp:function std::enable_if<CreateProcessorImplemented<char const*, void>::value, void>::type Process<char const*>(char const*): error: undefined reference to 'IProcessor<char const*>* CreateProcessor<char const*>()' 
collect2: error: ld returned 1 exit status 

Meine Idee ist, dass, wenn wir CreateProcessorImplemented<char const*> lösen, decltype(CreateProcessor<const char*>()) nicht, weil nicht versagt ist Ein Template-Prototyp IProcessor<T> CreateProcessor() und Compiler hält den Dekltyp gleich IProcessor<T>, das ist irgendwie logisch, aber nicht das, was ich brauche.

+0

'CreateProcessorImplemented' leitet sich immer von' std :: true_type', da eine Funktion müssen keine Implementierung haben für 'decltype' Ihnen zu sagen, den Rückgabetyp (siehe' std :: declval'). – Simple

+0

@Simple, das macht Sinn. Ich glaube, es gibt keine Möglichkeit zu überprüfen, ob die Funktion eine Implementierung während der Kompilierzeit hat, da es ein Wissen vom Linker erfordert, richtig? –

+0

Eine Alternative ist in der Antwort von @WojciechFrohberg. Sie verwenden eine "Struktur" mit einer statischen Elementfunktion anstelle einer Nichtmitgliedsfunktion. Sie können * erkennen, ob es keine statische Member-Funktion gibt, da die "Struktur" nicht spezialisiert ist. Es ist im Grunde eine Typeigenschaft. – Simple

Antwort

5

Eine Möglichkeit, es Arbeit zu machen ist Wrapper-Struktur zu verwenden CreateProcessor wie folgt zu funktionieren:

#include <cstdio> 
#include <type_traits> 

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t. 
template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

// An interface for a processor that receives a value of specific type. 
template<class T> 
class IProcessor { 
public: 
    virtual void process(T value) = 0; 
}; 

// A processor for int. 
class IntProcessor : public IProcessor<int> { 
public: 
    virtual void process(int value) override { 
     printf("IntProcessor::process is called for value = %d\n", value); 
    } 
}; 

// Template prototype. 
template<class T> 
struct ProcessorCreator: std::false_type { 
    static IProcessor<T>* CreateProcessor(); 
}; 

// Template specialization for int. 
template<> 
struct ProcessorCreator<int>: std::true_type { 
static IProcessor<int>* CreateProcessor() { 
    return new IntProcessor(); 
} 
}; 

// Detector of CreateProcessor. 
template<class, class=void> 
struct CreateProcessorImplemented : std::false_type { }; 

template<class T> 
struct CreateProcessorImplemented<T, typename std::enable_if<ProcessorCreator<T>::value>::type > : std::true_type { }; 


// Specializations depending on existence of CreateProcessor. 
template <typename T> 
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    IProcessor<T>* processor = ProcessorCreator<T>::CreateProcessor(); 
    processor->process(value); 
} 

template <typename T> 
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    printf("Processor for requested typename is unavailable\n"); 
} 


int main() { 
    Process(42); 
    Process("abc"); 

// static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
/* This static_assert fails with an error: 
* code.cpp:56:5: error: static assertion failed: :(
*  static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
*/ 
} 

Alternativ können Sie Template-Deklaration entfernen und übergeben Sie den IProcessor Template-Parameter-Typen mit der Funktion Überlastungen - durch die Schaffung von Scheinargument :

#include <cstdio> 
#include <type_traits> 

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t. 
template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

// An interface for a processor that receives a value of specific type. 
template<class T> 
class IProcessor { 
public: 
    virtual void process(T value) = 0; 
}; 

// A processor for int. 
class IntProcessor : public IProcessor<int> { 
public: 
    virtual void process(int value) override { 
     printf("IntProcessor::process is called for value = %d\n", value); 
    } 
}; 


IProcessor<int>* CreateProcessor(const int&) { 
    return new IntProcessor(); 
} 

// Detector of CreateProcessor. 
template<class, class=void> 
struct CreateProcessorImplemented : std::false_type { }; 

template<class T> 
struct CreateProcessorImplemented<T, void_t<decltype(CreateProcessor(std::declval<T>()))>> : std::true_type { }; 


// Specializations depending on existence of CreateProcessor. 
template <typename T> 
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    IProcessor<T>* processor = CreateProcessor(value); 
    processor->process(value); 
} 

template <typename T> 
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) { 
    printf("Processor for requested typename is unavailable\n"); 
} 


int main() { 
    Process(42); 
    Process("abc"); 

// static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
/* This static_assert fails with an error: 
* code.cpp:56:5: error: static assertion failed: :(
*  static_assert(!CreateProcessorImplemented<char const*>::value, ":("); 
*/ 
}