SFINAE to detect the existence of a non-member function

TL DR I want to write a template function Process(T value) that behaves differently for different values ​​depending on the existence of a function that is not a member of CreateProcessor<T>() . What can I do for this?

I have a problem with SFINAE. Suppose we need to support the CreateProcessor function, which returns an implementation of the IProcessor<T> interface for some type of type T

In C ++, we cannot create several function overloads that differ only in the return type, therefore we must make the CreateProcessor function also a template function parameterized by T

Now suppose we want to write a function of the template Process<T>(T value) , which works differently depending on the existence of CreateProcessor<T>() , namely: it must process value using a processor in case of the implementation of CreateProcessor<T>() , otherwise this should lead to an error.

I tried to write the following code:

 #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, ":("); */ } 

Although this leads to a binding error:

 /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 

My idea is that when we enable CreateProcessorImplemented<char const*> , decltype(CreateProcessor<const char*>()) does not fail, because there is a prototype of the IProcessor<T> CreateProcessor() template, and the compiler considers that the type decltype is equal to IProcessor<T> , which is something logical, but not what I need.

+6
source share
1 answer

One way to make it work is to use the wrapper construct for the CreateProcessor function as follows:

 #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, ":("); */ } 

Alternatively, you can remove the template declaration and pass the parameter type of the IProcessor protocol template using function overloads - by creating a dummy argument:

 #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, ":("); */ } 
+5
source

All Articles