I am looking into class template deduction available since C++17. Here are the code I would like to ask about:
#include <iostream>
#include <cmath>
using std::endl;
using std::cout;
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u) : t(std::forward<T>(u))
{ cout << "template" << endl;}
#ifdef ON
MyAbs(const T& t) : t(t) {}
#endif
T operator()() const
{
return std::abs(t);
}
T t;
};
/*
// may need the following
template<typename U>
MyAbs(U&&) -> MyAbs<typename std::remove_reference<U>::type>;
*/
int main()
{
const double d = 3.14;
cout << MyAbs(4.7)() << endl;
cout << MyAbs(d)() << endl;
return 0;
}
When MyAbs(const T&)
is not conditionally-compiled (i.e. no -DON
), both clang++ and g++ fail to deduce the template parameter, T
. Given -DON=1
, both compilers build the simple example above.
Firstly, I also guessed that the deduction should fail; the compiler could deduce U
but not T
. The compile errors I got were what I expected. Please, let me know if I am mistaken.
If I was correct on it, then, I cannot understand why the deduction with U&&
succeeds when MyAbs(const T&)
is added. What I expected was deducing with U&&
fails, and SFINAE allows me to invoke MyAbs(const T&)
instead for both cases: 4.7 and d. However, what happened is different. This program seems to invoke the template version for 4.7 and non-template version for d
.
$ g++ -Wall -std=c++17 ~/a.cc -DON=1
$ ./a.out
template
4.7
3.14
It seems that the template version has suddenly become viable. Is this expected? If so, what's the reason?
Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template.
Type inference or deduction refers to the automatic detection of the data type of an expression in a programming language. It is a feature present in some strongly statically typed languages. In C++, the auto keyword(added in C++ 11) is used for automatic type deduction.
" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.
Class template argument deduction happens in two stages.
Step 1 only helps you figure out the class template arguments. It does not do anything with regards to the actual constructor (or class template specialization) that may be used in step 2.
The existence of the constructors might drive the deduction, but if a given constructor is used to deduce that says nothing about whether or not it's used to construct.
So, when you just have:
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u);
};
Class template argument deduction fails - you have no deduction guide in your constructor, T
is a non-deduced context. The compiler just can't figure out what T
you want in MyAbs(4.7)
or MyAbs(d)
.
When you added this one:
template<typename T>
struct MyAbs {
template<typename U>
MyAbs(U&& u);
MyAbs(T const&);
};
Now it can! In both cases, it deduces T
as double
. And once it does that, then we go ahead and perform overload resolution as if we had typed MyAbs<double>
to begin with.
And here, MyAbs<double>(4.7)
happens to prefer the forwarding reference constructor (less cv-qualified reference) while MyAbs<double>(d)
happens to prefer the other one (non-template preferred to template). And that's okay and expected, just because we used one constructor for deduction doesn't mean we have to use specifically that constructor for construction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With