Function detection in C ++ at compile time

Is there a way, presumably using templates, macros, or a combination of them that I can basically apply to different classes of objects, but they can respond differently if they don’t have a specific function?

I specifically want to apply a function that will output the size of the object (ie the number of objects in the collection) if the object has this function, but will output a simple replacement (for example, "N / A") if the object does not work. I.e.

NO_OF_ELEMENTS( mySTLMap ) -----> [ calls mySTLMap.size() to give ] ------> 10 NO_OF_ELEMENTS( myNoSizeObj ) --> [ applies compile time logic to give ] -> "N/A" 

I expect this to be something similar to a static statement, although I would clearly like to compile a different code path rather than crashing at the build stage.

+6
source share
6 answers

From what I understand, you want to have a common test to find out if a class has a specific member function. This can be done in C ++ using SFINAE . In C ++ 11, this is pretty simple as you can use decltype :

 template <typename T> struct has_size { private: template <typename U> static decltype(std::declval<U>().size(), void(), std::true_type()) test(int); template <typename> static std::false_type test(...); public: typedef decltype(test<T>(0)) type; enum { value = type::value }; }; 

If you use C ++ 03, this is a bit more complicated due to the lack of decltype , so you need to use sizeof instead:

 template <typename T> struct has_size { private: struct yes { int x; }; struct no {yes x[4]; }; template <typename U> static typename boost::enable_if_c<sizeof(static_cast<U*>(0)->size(), void(), int()) == sizeof(int), yes>::type test(int); template <typename> static no test(...); public: enum { value = sizeof(test<T>(0)) == sizeof(yes) }; }; 

Of course, this uses Boost.Enable_If , which can be an undesirable (and unnecessary) dependency. However, write enable_if yourself is simple:

 template<bool Cond, typename T> enable_if; template<typename T> enable_if<true, T> { typedef T type; }; 

In both cases, the signature of the test<U>(int) method is visible only if U has a size method, because otherwise the evaluation is either decltype or sizeof (depending on the version you are using), which then removes this method from consideration (from for SFINAE . Long expressions std::declval<U>().size(), void(), std::true_type() are abuses of the C ++ comma operator, which will return the last expression from a comma-separated list, so this ensures that the type known as std::true_type for the C ++ 11 variant (and sizeof evaluates int for the C ++ 03 variant). void() in the middle is only there to convince There is no weird overload; the comma operator interferes with the evaluation.

Of course, this will return true if T has a size method that can be called without arguments, but does not give any guarantees regarding the return value. I assume that you probably want to find only those methods that do not return a void. This can easily be done with a slight modification to the test(int) method:

 // C++11 template <typename U> static typename std::enable_if<!is_void<decltype(std::declval<U>().size())>::value, std::true_type>::type test(int); //C++03 template <typename U> static typename std::enable_if<boost::enable_if_c<sizeof(static_cast<U*>(0)->size()) != sizeof(void()), yes>::type test(int); 
+12
source

Several times ago there was a discussion about the possibilities of constexpr . I have to use it, I think :)

It is easy to create a trait with constexpr and decltype :

 template <typename T> constexpr decltype(std::declval<T>().size(), true) has_size(int) { return true; } template <typename T> constexpr bool has_size(...) { return false; } 

So simple is it that the trait loses most of its meaning:

 #include <iostream> #include <vector> template <typename T> auto print_size(T const& t) -> decltype(t.size(), void()) { std::cout << t.size() << "\n"; } void print_size(...) { std::cout << "N/A\n"; } int main() { print_size(std::vector<int>{1, 2, 3}); print_size(1); } 

In action :

 3 N/A 
+9
source

This can be done using the SFINAE method. In your specific case, you can implement this using Boost.Concept Check . You need to write your own concept to test the size method. Alternatively, you can use an existing concept, such as Container , which, among other things, requires a size method.

+4
source

You can do something like

 template< typename T> int getSize(const T& t) { return -1; } template< typename T> int getSize( const std::vector<T>& t) { return t.size(); } template< typename T , typename U> int getSize( const std::map<T,U>& t) { return t.size(); } //Implement this interface for //other objects class ISupportsGetSize { public: virtual int size() const= 0; }; int getSize( const ISupportsGetSize & t ) { return t.size(); } int main() { int s = getSize( 4 ); std::vector<int> v; s = getSize( v ); return 0; } 

basically the most common implementation always returns -1 or "NA", but for a vector and maps it returns a size. Since the most common always matches, build time never fails

+3
source

Here you go. Replace std::cout with the output you like.

 template <typename T> class has_size { template <typename C> static char test( typeof(&C::size) ) ; template <typename C> static long test(...); public: enum { value = sizeof(test<T>(0)) == sizeof(char) }; }; template<bool T> struct outputter { template< typename C > static void output( const C& object ) { std::cout << object.size(); } }; template<> struct outputter<false> { template< typename C > static void output( const C& ) { std::cout << "N/A"; } }; template<typename T> void NO_OF_ELEMENTS( const T &object ) { outputter< has_size<T>::value >::output( object ); } 
+2
source

You can try something like:

 #include <iostream> #include <vector> template<typename T> struct has_size { typedef char one; typedef struct { char a[2]; } two; template<typename Sig> struct select { }; template<typename U> static one check (U*, select<char (&)[((&U::size)!=0)]>* const = 0); static two check (...); static bool const value = sizeof (one) == sizeof (check (static_cast<T*> (0))); }; struct A{ }; int main ( ) { std::cout << has_size<int>::value << "\n"; std::cout << has_size<A>::value << "\n"; std::cout << has_size<std::vector<int>>::value << "\n"; } 

but you have to be careful, this does not work when size overloaded, and also is not a template. When you can use C ++ 11, you can replace the sizeof trick with decltype magic

+1
source

Source: https://habr.com/ru/post/906314/


All Articles