Search code examples
c++templatesmetaprogrammingtemplate-specializationtype-traits

Template specialization for type traits where the type change depending on conditional


Assume we have a given class ABC that is templated with K and V:

template <typename K, typename V>
using ABC = AnotherClass<K, V>

I know that it is possible to implement type traits that check if a template T is of the given class ABC, eg:

template <typename T>
struct is_class_abc : std::false_type {};

template <typename K, typename V>
struct is_class_abc<ABC<K, V>> : std::true_type {};

Assume we change the ABC definition to be AnotherClass with types that change depending on K and V:

template <typename K, typename V>
using ABC = AnotherClass<typename std::conditional<
      CONDITION, SOMETHING, SOMETHIN_ELSE>::type, ... >

The condition, something and something_else all depend and rely on K-V.

I get this compile error:

Class template partial specialization contains template parameters that cannot be deduced; this partial specialization will never be used

Non-deducible template parameter 'K'

Same for V.

Any idea if it is possible to do this? If so, how do I make it work?


Solution

  • The partial specialization will not only check if the passed type is of the form ABC<K,V> but it also will try/need to deduce K and V. Now let's look at how this would work. Assume we pass the type trait should check ABC<K, V>. Since ABC is only a type alias, the actual type passed to this type trait will actually be of the form AnotherClass<K',V'> where K' and V' are some modification of K and V.

    Now the type trait would need to deduce K and V back from K' and V', this is really necessary since it is possible that there are no such K and V. But since you use the std::conditional<...>::type, it is not transparent to the compiler what is going on there. This is called a non-deduced context. Basically, there some easy cases where we can deduce in the backwards direction (e.g. if you habe ABC = AnotherClass<const K, V>). In many cases (such as when you use a typedef inside another type, like here) that is to complicated/not possible.

    This is not always intuitive and sometimes baffling, such as here:

    template<class>
    struct is_const : std::false_type {};
    
    template<class T>
    struct is_const<const T> : std::true_type {}; // This works
    
    template<class T>
    struct is_const<typename add_const<T>::type> : std::true_type {}; // This does NOT work
    

    The second specialization also only adds a const such as const T does, but this is hidden (aka in a non-deduced context) and hence we cannot deduce T.