I try to detect if a class has been implemented in C++.
The class has either only been declared before (case 1) or is implemented (case 2).
To detect the implementation of the class the SFINAE expression is supposed to evaluate sizeof(T)
for the template parameter which fails if the class is not complete.
#include <type_traits>
// 1. Feature not supported: Class only declared
class Test;
// 2. Feature supported: Class implemented
// class Test {};
// Detection if class is complete
template<class T, class Enable = void>
struct is_complete
{
static constexpr bool value = false;
};
template<class T>
struct is_complete<T, std::enable_if_t<(sizeof(T) == sizeof(T))>>
{
static constexpr bool value = true;
};
int main(void)
{
if constexpr (is_complete<Test>::value)
{
// static_assert(is_complete<Test>::value, "Test is not complete");
return 1;
}
return 0;
}
The problem with that is that the body of the consexpr if-condition in the main method is evaluated during compile time, even though it is not used during run time (the program returns 0 on execution).
If I include a static_assert
in the body it produces the following error message:
<source>:20:9: error: static_assert failed due to requirement 'is_complete<Test, void>::value' "Test is not complete"
static_assert(is_complete<Test>::value, "Test is not complete");
^ ~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1
So with that is_complete<Test>::value
seems to evaluate to false inside the if-condition body, while the same expression inside the if-condition is evaluated to true during compilation, but not during runtime.
I can reproduce this behavior in GCC 11.2 and CLANG 12.0.1.
I also tried to change sizeof(T) == sizeof(T)
to sizeof(T) > 0
with the same result.
I can observe the same behavior if I try to instantiate the class and then cast it to the base class of it as in the following code:
#include <memory>
class Base { public: virtual ~Base() = default; };
// 1. Feature not supported: Class only declared
class Test;
// 2. Feature supported: Class implemented
//class Test: public Base { public: virtual ~Test() = default; };
template<typename T>
std::unique_ptr<Base> Build(void)
{
if constexpr (is_complete_v<Test>)
{
return std::make_unique<Test>();
}
return std::unique_ptr<Base>();
}
The compiler then complains about the cast of the unique_ptr
, which works if the class has been implemented:
<source>: In function 'std::unique_ptr<Base> Build()':
<source>:33:38: error: could not convert 'make_unique<Test>()' from 'unique_ptr<Test,default_delete<Test>>' to 'unique_ptr<Base,default_delete<Base>>'
33 | return std::make_unique<Test>();
| ~~~~~~~~~~~~~~~~~~~~~~^~
| |
| unique_ptr<Test,default_delete<Test>>
Compiler returned: 1
So my questions are now:
- What exactly am I doing wrong?
- Is this a bug or a "feature" of the language?
- Is there a workaround for this limitation?