简体   繁体   中英

How to check if a class is declared in C++?

I am writing a piece of software around a framework and a class I am using (to be precise, extending) was renamed in a later version. Is there some way to write some macros/templates in C++11 in order to determine if a class with a certain name had been declared at the point in code?

An illustration of what I'm trying to accomplish follows. Let's say that file class_include.h contains either the definition of class A :

class A
{ 
...
};

or class B :

class B
{ 
...
};

and class C tries to extend whichever of those is declared:

#include <class_include.h>

#if (class A is declared)
class C : public A
#else // class B is declared
class C : public B
#endif
{
...
};

Note: It came to my mind to try and check a version of the framework but the answer to this question interests me. I also cannot change any framework header files.

EDIT: The accepted answer depends on whether the class is defined (which implies declaration) and, in my case, the class is declared iff it's defined.

In addition to the template magic ideas already given, the traditional approach is to use the library's "version" macros if possible. If there aren't any, can't you just change your code and start using the new version of the library? Denote the new version of the dependency in your build system as appropriate.

Ultimately, dependency control is a normal and expected part of the software deployment process. So, even though it can be a bit of a pain in the arse, I wouldn't overcomplicate your code solely in attempt to completely eliminate it. I mean you already have to list the library in some form as a dependency so you're halfway there before you've even started!

The other answers do technically achieve your goal, as long as we assume that "class A is defined" can be considered equivalent to "class A is defined and takes the form of exactly what we think it should take the form of". Without working dependency control you are already kind of screwed, and with it you do not need hacks.

You can, and with no macros required. First an observation, you can "forward" declare a class even after its full definition is available. Ie this is valid:

class foo{};
class foo;

Now, with the help of a homebrew void_t implementation and an is_complete type utility, you can do something like this:

#include <type_traits>

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

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

template <typename T>
struct is_complete<T, ::void_t<decltype(sizeof(T) != 0)>> : std::true_type {};

class A;
class B;

class C : public std::conditional<is_complete<A>::value, A, B>::type {
};

Depending on whether or not the full definition of A is present, C will inherit from A or B publicly. See a live example .

But I caution, this needs to be handled with care or you are very likely to have an ODR-violation in your program.

One way is to exploit SFINAE using typeid which will have a different result from an incomplete type:

#include <iostream>
#include <typeinfo> // for typeid

template<typename T, typename = void>
constexpr bool is_defined = false;

template<typename T>
constexpr bool is_defined<T, decltype(typeid(T), void())> = true;

struct complete {}; // i.e. `complete` is defined.
struct incomplete; // not defined, just a forward declaration

int main()
{
    std::cout << is_defined<complete> << " " << is_defined<incomplete>;
}

This requires you to forward declare the classes though, but as is_defined is constexpr it can be used at compile time. You could use sizeof too but I'm nervous about empty base class optimisations yielding false positives.

In your case, since you want to inherit from the class, it has to be declared but also defined; and this simplifies things quite a bit.

namespace detail_detectDefinedClass {
    template <class Void, class First, class... Rest>
    struct detect : detect<Void, Rest...> { };

    template <class First, class... Rest>
    struct detect<std::void_t<decltype(sizeof(First))>, First, Rest...> {
        using type = First;
    };
}

template <class... Classes>
using DetectDefinedClass = typename detail_detectDefinedClass::detect<
    std::void_t<>, Classes...
>::type;

struct A /* B */ {

};

class C : public DetectDefinedClass<struct A, struct B> {

};

static_assert(std::is_base_of_v<A, C>);
//static_assert(std::is_base_of_v<B, C>);

This uses SFINAE by trying to use sizeof on the requested type, which only works if the type has been defined ( struct A in the template's argument list merely declares it).

You could use the class code guard:

//classA.h
#ifndef cgA
#define cgA
class A {};
#endif

//classB.h
#ifndef cgB
#define cgB
class B {};
#endif

//classC.h
#include classA.h
#include classB.h

#ifdef cgA
class C : public A
#else
class C : public B
#endif

Edit with extra comment info: You could add an empty classA.h and classB.h and refer to them as last in your includepath. If you framework does not contain A or B the empty files will be loaded.

You may use a type alias and select the right one according to the library version

#ifdef LIB_VER_123 
typedef A A1;
#else
typedef B A1;
#endif

class C : public A1
{ 
   ...
}

If the type/class you want to check is defined in a single header, in C++-17, you can use __has_include . Example:

#ifdef __has_include( <header_that_defines_the_class.hpp> )
// here the class is defined
#else
// there the class is not defined
#endif

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM