Home > Back-end >  Is there a precedence to type_traits?
Is there a precedence to type_traits?

Time:10-28

I'm pretty new to SFINAE, and was wondering if there's a precidence to which template the compiler will select if multiple std::enable_if_t<true, std::is_...<T>> end up applying to T.

Like in this example:

template<typename T, typename = void>
class Thing {
    // Thing A
};

template<typename T>
class Thing<T, std::enable_if_t<true, std::is_scalar<T>> {
    // Thing B
};

template<typename T>
class Thing<T, std::enable_if_t<true, std::is_float<T>> {
    // Thing C
};

template<typename T>
class Thing<T, std::enable_if_t<true, std::is_signed<T>> {
    // Thing D
};

template<>
class Thing<float> {
    // Thing E
};


int main(){
    Thing<float> x;   // what order would the compiler try out?
}

I think it'll try to pick the most "specific" template specialization, so E first, and A last, but that's all I'm sure of. I don't know how it'd disambiguate from B, C, or D, or how to combine multiple conditions for T if I need to control the disambiguation process.

CodePudding user response:

To also answer the question: no, that kind of precedence does not exist. The action of enable_if, though clever, is to either fail or succeed the substitution. More importantly, the substitution occurs before any potential instantiations are compared with each other. The rules for this comparison are complicated, but in your example it does come down to choosing the most specialized option, using the following ranking:

  1. Thing A is the primary (least specialized) template;
  2. Things B, C and D are equally specialized, but more specialized than A;
  3. Thing E is the most specialized.

Therefore E will be selected. B, C and D must be considered because their conditions all evaluated to true, and they are equally specialized because they each have the single type parameter T. Since a valid program must have one unambiguous match for each use of a name, you will get a compiler error if you remove Thing E from the example.

Now, you can disambiguate B, C and D by using enable_if with more appropriate (combinations of) conditions, and the problem of ambiguity doesn't magically vanish by using concepts. However, you can use concepts directly with template parameters like so:

#include <concepts>

template<typename T>
class Thing {};         // Thing A

template<std::integral T>
class Thing<T> {};      // Thing B

template<std::signed_integral T>
class Thing<T> {};      // Thing C

template<std::floating_point T>
class Thing<T> {};      // Thing D

template<>
class Thing<float> {};  // Thing E

int main() {
    Thing<float>  E;
    Thing<double> D;
    Thing<int>    C;
    Thing<bool>   B;
    Thing<void>   A;
}

This is just a short example, but I think concepts are such a significant improvement to generic C that they should be taught and learned at the same time as templates. They are now widely supported, so you can always try them out on Compiler Explorer if you happen to have an older compiler.

  • Related