Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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.

like image 715
nm_tp Avatar asked Nov 14 '18 10:11

nm_tp


4 Answers

You can, and with no macros required. First an observation, you can "forward" declare a class even after its full definition is available. I.e. 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.

like image 78
StoryTeller - Unslander Monica Avatar answered Nov 05 '22 22:11

StoryTeller - Unslander Monica


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.

like image 43
Lightness Races in Orbit Avatar answered Nov 06 '22 00:11

Lightness Races in Orbit


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.

like image 6
Bathsheba Avatar answered Nov 06 '22 00:11

Bathsheba


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).

like image 3
Quentin Avatar answered Nov 05 '22 23:11

Quentin