I am trying to apply the type_trait has_fun
recursively so that C
enables its fun
member function only if T
has one.
Is there a way to make C::fun
being conditionally detected?
template <typename T>
struct has_fun {
template <class, class> class checker;
template <typename U>
static std::true_type test(checker<U, decltype(&U::fun)> *);
template <typename U>
static std::false_type test(...);
static const bool value = std::is_same<std::true_type, decltype(test<T>(nullptr))>::value;
};
struct A {
void fun(){
std::cout << "this is fun!" << std::endl;
}
};
struct B {
void not_fun(){
std::cout << "this is NOT fun!" << std::endl;
}
};
template<typename T>
struct C {
void fun() {
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
int main(int argc, char const *argv[])
{
std::cout << has_fun<A>::value << std::endl;
std::cout << has_fun<B>::value << std::endl;
std::cout << has_fun<C<A>>::value << std::endl;
std::cout << has_fun<C<B>>::value << std::endl;
}
Output:
1
0
1
1
Expected output:
1
0
1
0
You need to allow the compiler to SFINAE on the method.
All checks that happen in templates only take into account the signatures of the functions, so the static_assert that you used will not be considered.
The solution is to add a check in the signature.
Intuitively you would write
template<typename T>
struct C {
std::enable_if_t<has_fun<T>::value> fun() {
t.fun();
}
T t;
};
But this will not produce what you expect: the compiler will refuse to compile C, even if you don't call C.fun();
Why?
The compiler is allowed to evaluate code and issue errors if it can prove it will never work. Since when you declare C the compiler can prove that foo() will never be allowed, it will fail compilation.
To solve the issue you can force the method to have a dependent type, so that the compiler can't prove that it's always going to fail.
Here is the trick
template<typename T>
struct C {
template<typename Q=T, typename = if_has_fun<Q>>
void fun() {
t.fun();
}
T t;
};
The compiler can't prove that Q will always be T, and we check Q, not T, so the check is going to be performed only when you invoke fun.
Full working solution at https://wandbox.org/permlink/X32bwCqQDb288gVl
Note: I used the detector which is in experimental, but you can use your detector.
You need to replace the true test though, in order to check that the function can be properly called.
template <typename U>
static std::true_type test(checker<U, decltype(std::declval<U>().fun())> *);