echo Lee
echo Lee

Reputation: 341

template method "=0" in the parameter list

I saw code like the following:

using EnableIfIntegral = typename std::enable_if<
    std::is_integral<T>::value || std::is_enum<T>::value, int>::type;

template <typename T, EnableIfIntegral<T> = 0>
constexpr int Seconds(T n) {
  return time_internal::FromInt64(n, std::ratio<1>{});
}

template <std::intmax_t N>
constexpr int FromInt64(int64_t v, std::ratio<1, N>) {
  // Do Something
}

I understand what a template function is. Why in the template parameter list, does it have SomeClass<T> = 0? I know T is the template parameter.

Why is std::ratio<1, N> a parameter?

Upvotes: 4

Views: 732

Answers (1)

Sam Varshavchik
Sam Varshavchik

Reputation: 118445

I'll answer the first of your two questions.

using EnableIfIntegral = typename std::enable_if<
    std::is_integral<T>::value || std::is_enum<T>::value, int>::type;

I'm fairly certain that you accidentally left out the previous line, which should be

template<typename T>

So the full declaration is

template<typename T>
using EnableIfIntegral = typename std::enable_if<
    std::is_integral<T>::value || std::is_enum<T>::value, int>::type;

This is pretty big pill to swallow, and I'll come back and discuss the finer details of this declaration in a moment, but what happens here is that if the stars and the moons are alined just right, this becomes:

template<typename T>
using EnableIfIntegral = int;

In other words, a template type that's just a simple alias for an garden variety int. Nothing more than that. Moving onto to the next declaration:

template <typename T, EnableIfIntegral<T> = 0>
constexpr int Seconds(T n) {

This simply becomes

template <typename T, int = 0>
constexpr int Seconds(T n) {

In other words, a simple template parameter, an int constant that defaults to 0.

That's it, and nothing more. This does absolutely nothing, and that's precisely the intended result here.

The first template parameter, T gets deduced from the actual parameter to the Seconds() template function. Then the 2nd template parameter passes the T template type to the EnableIfIntegral template alias declaration, which, hopefully, if all the stars and the moons are properly aligned, does nothing.

Now, when do the stars and the moons properly align? It's when the deduced template type is either some integral type or some enum type. Then everything just works.

But if you do something silly, like:

std::string what_is_this;

Seconds(what_is_this);

A very loose description of what will happen: the first template parameter to Seconds gets deduced as std::string. So far so good, but we're already doomed because what's inside the Seconds template function will not be able to handle a std::string value. Normally this will typically result in a typical, indecipherable (to mere mortals) C++ compiler error message.

But things are not going to get this far. What happens first is that the deduced T template parameter, which is now std::string, gets forwarded to EnableIfIntegral, and this template alias will fail to resolve. For all the gory details why, look up what std::enable_if does in your favorite C++ book. The exact details of how that happens is not very important, the point is that the template substitution for the Seconds() template itself will fail. The compiler will not find a suitable Seconds template that will match this function call.

This situation typically produces a much simpler error message from your compiler. Your compiler's error message will be much basic, something along the lines "Hey, what's this Seconds template being called here? I know nothing about it". You will look at the code and realize "Silly me, I'm passing a std::string but I need to pass an integral value instead". Oops.

Overall, what this is, is just a common approach to make the pain of using a C++ library less painful, when things go wrong and don't compile. Without this trick, the compiler will whine about the time_internal::FromInt64 function call (based on the shown code in the question), but there's nothing wrong with that function call. The problem is really with whatever calls Seconds, that's where the problem is, and this common approach helps the compiler produce a better error message.

Upvotes: 6

Related Questions