I'm trying to combine the approaches used in this answer for detecting whether a class has a member variable x
with this answer to select different implementations depending on that using enable_if
.
Basically, I want to write a trait class that, given a type T
, provides access to the member T::x
if it exists, and provides a default value otherwise.
The following code does not compile on g++: (Compiler Explorer)
#include <iostream>
#include <type_traits>
// classes with / without x member
struct WithX { static constexpr int x = 42; };
struct WithoutX {};
// trait to detect x
template <typename T, typename = void>
struct HasX : std::false_type { };
template <typename T>
struct HasX <T, decltype((void) T::x)> : std::true_type { };
// trait to provide default for x
template <typename T>
struct FooTraits
{
template <bool enable = HasX<T>::value>
static constexpr std::enable_if_t< enable, size_t> x() { return T::x; }
template <bool enable = HasX<T>::value>
static constexpr std::enable_if_t<!enable, size_t> x() { return 1; }
};
int main() {
std::cout << HasX<WithX>::value << std::endl;
// Uncomment the following line to make this compile with g++
//std::cout << HasX<WithoutX>::value << std::endl;
std::cout << FooTraits<WithoutX>::x() << std::endl;
}
g++ gives error messages that
error: 'x' is not a member of 'WithoutX'
struct HasX <T, decltype((void) T::x)> : std::true_type { };
in the part which should detect whether x
is a member in the first place. Curiously though, if I uncomment the second to last line which instantiates HasX<WithoutX>::value
by itself, g++ compiles without errors (Compiler Explorer).
Both clang and msvc compile without a problem on Compiler Explorer.
What's wrong here?
SFINAE only work in immediate context. In other words, if the compiler can see in advance that a declaration has a problem, then it must be an error.
When instantiating a class, the compiler will try to resolve everything it can. So is this:
template<bool enable = HasX<T>::value>
....
This is not dependent of the context of the function. This can be instantiated right when FooTraits
is instantiated.
In other words, this assignation can be calculated in advance, as if you'd move it to the class scope.
In your case, the compiler has nothing to do at subtitution.
The fix is simply that:
template <typename U = T, bool enable = HasX<U>::value>
static constexpr std::enable_if_t< enable, size_t> x() { return T::x; }
template <typename U = T, bool enable = HasX<U>::value>
static constexpr std::enable_if_t<!enable, size_t> x() { return 1; }
In theory, U
could be a completely different type. U
is immediate to the function instantiation, while T
is not.
Fact that switching comment of:
//std::cout << HasX<WithoutX>::value << std::endl;
is indeed a good sign of gcc bug.
Seems gcc has issue with your form:
template <typename T>
struct HasX <T, decltype((void) T::x)> : std::true_type {};
A more typical way is to use std::void_t
:
template <typename T>
struct HasX <T, std::void_t<decltype(T::x)>> : std::true_type {};
which indeed solves the issue Demo.
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