Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function template overload resolution with a pointer argument

The following code demonstrates the core of a C++ template metaprogramming pattern I have been using to determine whether a type T is an instantiation of a specific class template:

#include <iostream>

template<class A, class B>
struct S{};

template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}

template<class T>
constexpr bool isS(const T*) {return false;}

int main() {
  S<int,char> s;
  std::cout<<isS(&s)<<std::endl;
  return 0;
}

It features two overloads of a constexpr function template isS, and it outputs 1, as expected. If I remove the pointer from the second isS, i.e. replace it with

template<class T>
constexpr bool isS(const T) {return false;}

the program unexpectedly outputs 0. If both versions of isS make it through to the overload resolution phase of compilation, then the output implies that the compiler is choosing the second overload. I have tested this under GCC, Clang and vc++ using the online compilers here, and they all produce the same result. Why does this happen?

I have read Herb Sutter's "Why Not Specialize Function Templates" article several times, and it seems that both isS functions should be considered to be base templates. If this is so, then it is a question of which one is the most specialised. Going by intuition and this answer, I would expect the first isS to be the most specialised, because T can match every instantiation of S<A,B>*, and there are many possible instantiations of T that cannot match S<A,B>*. I'd like to locate the paragraph in the working draft that defines this behaviour, but I'm not entirely sure what phase of compilation is causing the problem. Is it something to do with "14.8.2.4 Deducing template arguments during partial ordering"?

This issue is particularly surprising given that the following code, in which the first isS takes a reference to const S<A,B> and the second takes a const T, outputs the expected value 1:

#include <iostream>

template<class A, class B>
struct S{};

template<class A, class B>
constexpr bool isS(const S<A,B>&) {return true;}

template<class T>
constexpr bool isS(const T) {return false;}

int main() {
  S<int,char> s;
  std::cout<<isS(s)<<std::endl;
  return 0;
}

So the problem seems to be something to do with how pointers are treated.

like image 436
Ose Avatar asked Apr 06 '16 12:04

Ose


People also ask

Can we overload template functions with the same of arguments?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.

Can you overload a template function?

A template function can be overloaded either by a non-template function or using an ordinary function template.

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.

What is the relationship between function templates and overloading?

Function overloading is used when multiple functions do similar operations; templates are used when multiple functions do identical operations. Templates provide an advantage when you want to perform the same action on types that can be different.


Video Answer


1 Answers

Because the second overload will drop the top-level const inside the const T, it will resolve to T* during argument deduction. The first overload is a worse match because it will resolve to S<int, char> const*, which requires a const-qualification conversion.

You need to add const in front of your variable s in order for the more specialized overload to kick in:

#include <iostream>

template<class A, class B>
struct S {};

template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}

//template<class T>
//constexpr bool isS(const T*) {return false;}

template<class T>
constexpr bool isS(const T) {return false;}

int main() {
  S<int,char> const s{}; // add const here
  std::cout<<isS(&s)<<std::endl;
  return 0;
}

Live Example

Changing the first overload to a const S<A,B>&, will give the correct result because there is an identity conversion instead of a qualification adjustment.

13.3.3.1.4 Reference binding [over.ics.ref]

1 When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion (13.3.3.1).

Note: when in doubt about such argument deduction games, it's handy to use the __PRETTY_FUNCTION__ macro which (on gcc/clang) will give you more information about the deduced types of the selected template. You can then comment out certain overloads to see how this affects the overload resolution. See this live example.

like image 128
TemplateRex Avatar answered Sep 18 '22 12:09

TemplateRex