Why does the has_some_type
trait fail?
It's because the deduction of the second template argument only uses the primary template, and doesn't "take hints" from your template specialization.
Think of your partial specialization of has_some_type
when you substitute X
for C
:
template <>
struct has_some_type<X, int> : std::true_type {};
this substitution doesn't fail, but - why would it match has_some_type<X>
? When the compiler sees has_some_type<X>
, it will ruthlessly ignore your attempts to catch its attention, and simply use the = void
for deducing the second template parameter.
Why does the has_some_function
trait succeed?
Look at the second type you use in the specialization: decltype(std::declval<C>().some_function())
. What is that type resolve to, for X
? ... yes, it resolves to void
. So when you substitute X
for C
, it's:
template <>
struct has_some_function<X, void> : std::true_type {};
and this will match has_some_function<X>
, using the = void
from the primary template.
How can we fix the has_some_type
trait?
While C++17 and C++20 offer easier solutions (as suggested by @Frank and @Quimby) - let's stick to C++14 and think about fixing what we've already written.
From the has_some_function
example, we might be inspired to "fix" the type trait by replacing the primary template with template <typename T, typename = int>
; but while this would work for X
and Y
, it would not work if you had using some_type = double
or any other non-int type. And that's not what we want :-(
So, can we have a type whose definition is: "check the validity of some_type
, whatever it may be, but eventually just behave like a single fixed type"? ... Yes, it turns out that we can.
#include <type_traits>
struct X {
using some_type = int;
};
struct Y {};
template<typename T>
using void_t = void;
// Please Mr. Compiler, make sure T is a legit type, then use void.
template<typename, typename = void>
struct has_some_type : std::false_type { };
template<typename T>
struct has_some_type<T, void_t<typename T::some_type>> : std::true_type { };
static_assert(has_some_type<X>::value, "Unfortunately, X doesn't have some_type");
static_assert(has_some_type<Y>::value, "Unfortunately, Y doesn't have some_type");
Only the second assertion fails.
See this in action on GodBolt.
Notes:
- This solution is actually valid C++11.
- C++17 introduced
std::void_t
to save you some typing. It also supports variadic parameter packs rather than just single types.
- If you use an old compiler, the code above may fail due to C++ standard defect CWG1558.