Search code examples
c++templatestype-traits

Type traits and template overload resolution


I'm writing a type trait to check that a given type is a specific container and I came across the following situation:

#include <array>
#include <cstddef>

template<typename T>
struct is_char_array_t final: std::false_type {};
template<std::size_t N>
struct is_char_array_t<std::array<char, N>> final: std::true_type {};

int main(){
    if constexpr (is_char_array_t<42>) {
        return 2;
    } 
}

This code doesn't compile, the error it gives is:

<source>: In function 'int main()':
<source>:10:37: error: type/value mismatch at argument 1 in template parameter list for 'template<class T> struct is_char_array_t'
   10 |     if constexpr (is_char_array_t<42>) {
      |                                     ^
<source>:10:37: note:   expected a type, got '42'
<source>:10:38: error: expected unqualified-id before ')' token
   10 |     if constexpr (is_char_array_t<42>) {
      |                                      ^
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:10:37: error: type/value mismatch at argument 1 in template parameter list for 'template<class T> struct is_char_array_t'
   10 |     if constexpr (is_char_array_t<42>) {
      |                                     ^
<source>:10:37: note:   expected a type, got '42'
<source>:10:38: error: expected unqualified-id before ')' token
   10 |     if constexpr (is_char_array_t<42>) {
      |                                      ^
Execution build compiler returned: 1

The behavior is what I want, since it doesn't let anything that isn't a std::array<char, N> match the trait, but I'm left wondering why it works.

I would think that the template overload for std::size_t is a better match for the value 42 than the template that requires a type. So why doesn't the second, more specialized template match the given value?

Here's a godbolt with the example: https://godbolt.org/z/KdKvrPsx3


Solution

  • This is the template:

    template<typename T>
    struct is_char_array_t final: std::false_type {};    // (I)
    

    And this is a specialization:

    template<std::size_t N>
    struct is_char_array_t<std::array<char, N>> final: std::true_type {};
    

    The specialization is not a new template. It does not change the fact that is_char_array has one type argument. It merely specializes the primary template (I) for the case that T matches std::array<char,N>. The specialization starts with template <std::size_t N> because N is a "free parameter", you do not care what N is for the specialization as long as the type T is a std::array<char,N> for some N. In other words, it is a partial specialization. A full specialization would start with template <> and have no such "free parameter". You can identify the specialization by the is_char_array_t<std::array<char, N>> where the primary template only mentions the name of the template.

    Your code does not really "work". The trait is used to yield either true or false, depending on the type that is used to instantiate it. Using a value is an error.