How to Detect Existence of a Class Using Sfinae

How to detect existence of a class using SFINAE?

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

Templated check for the existence of a class member function?

Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:

#include <iostream>

struct Hello
{
int helloworld() { return 0; }
};

struct Generic {};

// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };

template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);

public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}

I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.

Using SFINAE to check if member exists in class based on a template

The template parameters of the template template parameter can not be used in the body.

template <typename T, typename = void>
struct has_psl : std::false_type
{};

template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};

template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}

And then,

static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);

Online Demo

Use SFINAE to detect the existence of a templated member function

First of all, showing you a shortened version of your original code:

template <typename T>
struct has_method_hello {

static constexpr auto test(int) -> decltype(std::declval<T&>().hello(), std::true_type());

static constexpr std::false_type test(...);

using result_type = decltype(test(0));
static const bool value = result_type::value;

};

struct Foo {
void hello() {}
};

Now making it work for a template parameter, easy, an example:

template <typename T>
struct has_method_hello {

static constexpr auto test(int) -> decltype(std::declval<T&>().hello(std::declval<int&>()), std::true_type());

static constexpr std::false_type test(...);

using result_type = decltype(test(0));
static const bool value = result_type::value;

};

struct Foo {
template <typename T>
void hello(T& v) {}
};

Note that, I have hardcoded int type here. You can make that part of has_method_hello template too.

Why using SFINAE to find if a method exists fails with std::vector::begin

std::vector has an oveloaded begin: one overload is const and the other one is not.

The compiler doesn't know which overload to use when you write &C::begin, so it treats this ambiguity as an error, which gets detected by SFINAE.

Instead of forming a pointer to begin, just call it:

// ...
template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * );
// ...

(In case it's not obvious, if you're trying to detect a function with parameters, you must provide arguments, e.g. .resize(0) (or perhaps .resize(std::size_t{})) instead of just .resize().)

And here's another, shorter solution:

#include <iostream>
#include <vector>

template <typename T, typename = void>
struct has_begin : std::false_type {};

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
std::cout << has_begin<std::vector<int>>::value << std::endl;
}

And here's a fancy C++20 requires-based solution:

#include <iostream>
#include <vector>

template <typename T>
concept has_begin = requires(T t) {t.begin();};

int main(int argc, char *argv[])
{
std::cout << has_begin<std::vector<int>> << std::endl;
}

Test if a not class member function exists (SFINAE)

You may use something like this:

template <class T>
static auto hasPrintMethod(int)
->std::integral_constant<bool, std::is_class<decltype(print(T()))>::value>;

template <class>
static auto hasPrintMethod(...)->std::false_type;

template <typename T>
struct HasPrintMethod : decltype(hasPrintMethod<T>(0)) {
};

Here the decltype(print(T())) is std::string for classes for which your non-member function is defined, and an erroneous type for other classes. So, according to SFINAE concept, HasPrintMethod<A>::value is equal to true for class A with print function defined and is equal to false otherwise.

Using SFINAE to detect existence of a function of void return type

nlohmann::json has multiple ways to convert a given type to json. There is no to_json defined for int, so your type trait is working as specified.

Instead, detect if you can convert the type to a nlohmann::json object:

template <typename T>
using is_jstreamable = std::is_convertible<T, nlohmann::json>;

Live on Godbolt

Using SFINAE to check if member exists in class based on a template

The template parameters of the template template parameter can not be used in the body.

template <typename T, typename = void>
struct has_psl : std::false_type
{};

template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};

template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}

And then,

static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);

Online Demo

How to detect whether there is a specific member variable in class?

Another way is this one, which relies on SFINAE for expressions too. If the name lookup results in ambiguity, the compiler will reject the template

template<typename T> struct HasX { 
struct Fallback { int x; }; // introduce member name "x"
struct Derived : T, Fallback { };

template<typename C, C> struct ChT;

template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1];
template<typename C> static char (&f(...))[2];

static bool const value = sizeof(f<Derived>(0)) == 2;
};

struct A { int x; };
struct B { int X; };

int main() {
std::cout << HasX<A>::value << std::endl; // 1
std::cout << HasX<B>::value << std::endl; // 0
}

It's based on a brilliant idea of someone on usenet.

Note: HasX checks for any data or function member called x, with arbitrary type. The sole purpose of introducing the member name is to have a possible ambiguity for member-name lookup - the type of the member isn't important.



Related Topics



Leave a reply



Submit