Is it possible to detect if a class exists in C++ using SFINAE? If possible then how?
Suppose we have a class that is provided only by some versions of a library. I'd like to know if it is possible to use SFINAE to detect whether the class exists or not. The result of detection is arbitrary, say an enum constant which is 1 if it exists, 0 otherwise.
If we ask the compiler to tell us anything about a class type T
that has not even been declared we are bound to get a compilation error. There is no way around that. Therefore if we want to know whether class T
"exists", where T
might not even have been declared yet, we must declare T
first.
But that is OK, because merely declaring T
will not make it "exist", since what we must mean by T
exists is T
is defined. And if, having declared T
, you can then determine whether it is already defined, you need not be in any confusion.
So the problem is to determine whether T
is a defined class type.
sizeof(T)
is no help here. If T
is undefined then it will give an incomplete type T
error. Likewise typeid(T)
. Nor is it any good crafting an SFINAE probe on the type T *
, because T *
is a defined type as long as T
has been declared, even if T
isn't. And since we are obliged to have a declaration of class T
, std::is_class<T>
is not the answer either, because that declaration will suffice for it to say "Yes".
C++11 provides std::is_constructible<T ...Args>
in <type_traits>
. Can this offer an off-the-peg solution? - given that if T
is defined, then it must have at least one constructor.
I'm afraid not. If you know the signature of at least one public constructor of T
then GCC's <type_traits>
(as of 4.6.3) will indeed do the business. Say that one known public constructor is T::T(int)
. Then:
std::is_constructible<T,int>::value
will be true if T
is defined and false if T
is merely declared.
But this isn't portable. <type_traits>
in VC++ 2010 doesn't yet provide std::is_constructible
and even its std::has_trivial_constructor<T>
will barf if T
is not defined: most likely when std::is_constructible
does arrive it will follow suit. Furthermore, in the eventuality that only private constructors of T
exist for offering to std::is_constructible
then even GCC will barf (which is eyebrow raising).
If T
is defined, it must have a destructor, and only one destructor. And that destructor is likelier to be public than any other possible member of T
. In that light, the simplest and strongest play we can make is to craft an SFINAE probe for the existence of T::~T
.
This SFINAE probe cannot be crafted in the routine way for determining whether T
has an ordinary member function mf
- making the "Yes overload" of the SFINAE probe function takes an argument that is defined in terms of the type of &T::mf
. Because we're not allowed to take the address of a destructor (or constructor).
Nevertheless, if T
is defined, then T::~T
has a type DT
- which must be yielded by decltype(dt)
whenever dt
is an expression that evaluates to an invocation of T::~T
; and therefore DT *
will be a type also, that can in principle be given as the argument type of a function overload. Therefore we can write the probe like this (GCC 4.6.3):
#ifndef HAS_DESTRUCTOR_H #define HAS_DESTRUCTOR_H #include <type_traits> /*! The template `has_destructor<T>` exports a boolean constant `value that is true iff `T` has a public destructor. N.B. A compile error will occur if T has non-public destructor. */ template< typename T> struct has_destructor { /* Has destructor :) */ template <typename A> static std::true_type test(decltype(std::declval<A>().~A()) *) { return std::true_type(); } /* Has no destructor :( */ template<typename A> static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test<T>(0)) type; static const bool value = type::value; /* Which is it? */ }; #endif // EOF
with only the restriction that T
must have a public destructor to be legally invoked in the argument expression of decltype(std::declval<A>().~A())
. (has_destructor<T>
is a simplified adaptation of the method-introspecting template I contributed here.)
The meaning of that argument expression std::declval<A>().~A()
may be obscure to some, specifically std::declval<A>()
. The function template std::declval<T>()
is defined in <type_traits>
and returns a T&&
(rvalue-reference to T
) - although it may only be invoked in unevaluated contexts, such as the argument of decltype
. So the meaning of std::declval<A>().~A()
is a call to ~A()
upon some given A
. std::declval<A>()
serves us well here by obviating the need for there to be any public constructor of T
, or for us to know about it.
Accordingly, the argument type of the SFINAE probe for the "Yes overload" is: pointer to the type of the destructor of A
, and test<T>(0)
will match that overload just in case there is such a type as destructor of A
, for A
= T
.
With has_destructor<T>
in hand - and its limitation to publicly destructible values of T
firmly in mind - you can test whether a class T
is defined at some point in your code by ensuring that you declare it before asking the question. Here is a test program.
#include "has_destructor.h" #include <iostream> class bar {}; // Defined template< class CharT, class Traits > class basic_iostream; //Defined template<typename T> struct vector; //Undefined class foo; // Undefined int main() { std::cout << has_destructor<bar>::value << std::endl; std::cout << has_destructor<std::basic_iostream<char>>::value << std::endl; std::cout << has_destructor<foo>::value << std::endl; std::cout << has_destructor<vector<int>>::value << std::endl; std::cout << has_destructor<int>::value << std::endl; std::count << std::has_trivial_destructor<int>::value << std::endl; return 0; }
Built with GCC 4.6.3, this will tell you that the 2 // Defined
classes have destructors and the 2 // Undefined
classes do not. The fifth line of output will say that int
is destructible, and the final line will show that std::has_trivial_destructor<int>
agrees. If we want to narrow the field to class types, std::is_class<T>
can be applied after we determine that T
is destructible.
Visual C++ 2010 does not provide std::declval()
. To support that compiler you can add the following at the top of has_destructor.h
:
#ifdef _MSC_VER namespace std { template <typename T> typename add_rvalue_reference<T>::type declval(); } #endif
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With