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