The code in this question is based on this answer. I am a little confused about how this produces the output it does, and whether it is all well defined
#include <type_traits>
#include <iostream>
#include <vector>
struct bar {};
void foo(bar) {}
struct moo {};
template<class T>
struct is_fooable {
static std::false_type test(...);
template<class U>
static auto test(const U& u) -> decltype(foo(u), std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
template<class T> inline constexpr bool is_fooable_v = is_fooable<T>::value;
template <typename T>
std::enable_if_t<!is_fooable_v<T>,void> foo(T) {}
int main() {
std::cout << is_fooable_v<bar>;
std::cout << is_fooable_v<moo>;
foo(bar{});
foo(moo{});
}
Output with gcc (same with clang and msvc):
10
If is_fooable_v<moo> is false then SFINAE does not discard the foo template and then moo is "fooable", though is_fooable_v<moo> is false nevertheless.
I find it confusing that the trait has only limited use, because it cannot tell if moo is "fooable" after it was used to define foo<T> with T==moo. Irrespective of that potential confusion, is the code well defined?
Is it ok to define a function based on a trait that tests if the function does exist?
Before I give the answer let me do a recap on why the code works like that:
The "problem" lies in these two lines:
Now imagine the
foo(moo)is being created by the second line. It has to executestd::enable_if<!is_fooable_v<T>, void>which has in turn execute!is_fooable_v<T>. What is the value of such expression withT = moo? Of course to find that out we have to instantiateis_fooable_v<moo>. After some calculations the compiler instantiates this boolean tofalse.And that's it: now the
is_fooable_v<moo>since that point is now equalfalsein every other evaluation of that expression (temp.inst#7). So when you use it in themain()function:it became "fixed" to that value.
To prove that look what if you add to those two lines this extra line:
this of course makes the
foo(moo{});line unable to compile, but it makes also both lines withstd::coutprinting ones.Look for yourself.
As for the question:
Well, the answer depends if you can actually find some use for this "trick".
If, for example, you are making a library then using this technique you can make a default implementation of a function based on if the user of your library actually provides his/her own. Of course there is a catch, I wanted to show in my recap above: you can't use the
is_fooable_v<>template in your code to test for existence of such user-provided function.