Implications of conversion function template argument deduction in C++ - c++

I'm having trouble understanding the implications of the conversion function template argument deduction rules in the C++ standard. The standard states that ([temp.deduct.conv] clause 1, §14.8.2.3.1 in N4594):
Template argument deduction is done by comparing the return type of the conversion function template (call it P) with the type that is required as the result of the conversion (call it A; see 8.5, 13.3.1.5, and 13.3.1.6 for the determination of that type) as described in 14.8.2.5.
where 14.8.2.5 ([temp.deduct.type]) is the section that describes general template argument deduction (though the most common case, function call template argument deduction [temp.deduct.call], no longer seems to point there; did it ever?). The next clause is what confuses me, though (clause 2):
If P is a reference type, the type referred to by P is used in place of P for type deduction and for any further references to or transformations of P in the remainder of this section.
To me, this seems to imply that template <class T> operator T() and template <class T> operator T&() are the same (and specifying both would result in an ambiguity). But that isn't the case in any compiler I've used! For instance:
struct any1 { template <typename T> operator T() { } };
struct any2 { template <typename T> operator T&() { } };
void f1(int) { }
void f2(int&) { }
void f3(int const&) { }
int main() {
f1(any1());
// f2(any1()); compile time error
f3(any1());
f1(any2());
f2(any2());
f3(any2());
}
Live Demo
But if references are ignored, any1 and any2 should have the same behavior, right? Clearly they don't, since f2(any1()) doesn't compile with either gcc or clang, while f2(any2()) compiles fine with both.
The next clause (clause 3, particularly 3.3) confuses things even further:
If A is not a reference type: [...] If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.
This, along with clause 2 about the removal of references, would seem to imply that the following code should not compile because of an ambiguity:
struct any3 {
template <typename T> operator T&() { }
template <typename T> operator T const&() { }
};
void f1(int) { }
int main() {
f1(any3());
}
Live Demo
And yet this works fine with both gcc and clang.
What am I missing?
Edit
I should clarify that the way the clang and gcc compilers handle this is exactly what I would expect from a general (relatively advanced) understanding of C++. Some commenters have asked for clarification on what my confusion is (and, implicitly, why I should care). My confusion here is entirely related to trying to understand the implications of the standard. I need a clear understanding of this because I am submitting a paper with code that relies heavily on this working and on my use of it being standards-compliant.

The key point you're missing is that overload resolution still has to happen. Template deduction isn't the end of the story. Addressing both of your examples separately:
To me, this seems to imply that template <class T> operator T() and template <class T> operator T&() are the same (and specifying both would result in an ambiguity). But that isn't the case in any compiler I've used!
The text you cite indicates that deduction of T is the same for both conversion operators, this is true. But the operators themselves are not the same. You have to additionally consider the rules for binding to references, which are enumerated in [dcl.init.ref]. The section is too long to concisely copy, but the reason that this is an error
f2(any1()); // error
is the same reason that f2(1) is an error: you can't bind an lvalue reference to non-const to an rvalue. As a result, even having both operators isn't in of itself ambiguous:
struct X {
template <class T> operator T(); // #1
template <class T> operator T&(); // #2
};
f1(X{}); // error: ambiguous
f2(X{}); // ok! #1 is not viable, calls #2
f3(X{}); // ok! #2 is preferred (per [dcl.init.ref]/5.1.2)
And yet this works fine with both gcc and clang.
struct any3 {
template <typename T> operator T&(); // #3
template <typename T> operator T const&() // #4
};
void f1(int) { }
int main() {
f1(any3());
}
This is an interesting scenario as far as compilers go, because gcc has a bug here. Both candidates should be valid (gcc doesn't consider #4 valid due to 61663). None of the tiebreakers apply on determining best viable candidate, so in this case we have to fall back to [temp.deduct.partial] to determine which candidate is more specializated... which, in this case, is #4.

Template argument deduction for a function template is just one step in the complex process of overload resolution.
§13.3.1 Candidate functions and argument lists
...
7 In each case where a candidate is a function template, candidate function
template specializations are generated using template argument deduction
(14.8.3, 14.8.2).
Template argument deduction is performed for a given function template as if no other function template exists. Reread the section §14.8.2.3 with that in mind, and you will realize that your questions belong to a different part of the standard.
After template argument deduction is performed for all candidate template functions, the best viable function must be selected according to the rules of §13.3.3. If by this time two or more function template specializations are present in the candidate function list, then the best viable function selection process involves partial ordering rules described in §14.5.6.2 (I think it's this section that contains answers to you questions).

Type deduction is a separate step from overload resolution and semantic checking.
struct any1 { template <typename T> operator T() { } };
struct any2 { template <typename T> operator T&() { } };
void f1(int) { }
void f2(int&) { }
void f3(int const&) { }
int main() {
f1(any1());
// f2(any1()); compile time error
f3(any1());
f1(any2());
f2(any2());
f3(any2());
}
Here f2(any1()) and f2(any2()) do behave identically for type deduction. Both deduce T=int. But then that T is substituted into the original declaration to get member specializations any1::operator int() and any2::operator int&(). f2(any1().operator int()) is a semantic error because it attempts to bind a non-const lvalue reference function parameter to an rvalue expression. This makes operator int() a non-viable function; if any1 had other conversion functions, they could be selected by overload resolution.
struct any3 {
template <typename T> operator T&() { }
template <typename T> operator T const&() { }
};
void f1(int) { }
int main() {
f1(any3());
}
Here again, the two template conversion functions do behave identically for type deduction. Both deduce T=int. Then that deduction is substituted into the original declarations to get operator int&() and operator int const&(). Then overload resolution compares those two. By my reading of Clause 13, they are ambiguous, but gcc chooses operator int&() and clang chooses operator int const&()...

Related

Why are variadic templates different than non-variadic, for only one argument?

This code compiles just fine:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename T>
void NonVariadicFunc(Struct<T>);
int main() {
NonVariadicFunc<int>(ConvertsToStruct{});
return 0;
}
But an attempt to make it a little more generic, by using variadic templates, fails to compile:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename... T>
void VariadicFunc(Struct<T...>);
int main() {
VariadicFunc<int>(ConvertsToStruct{});
return 0;
}
What's going wrong? Why isn't my attempt to explicitly specify VariadicFunc's template type succeeding?
Godbolt link => https://godbolt.org/g/kq9d7L
There are 2 reasons to explain why this code can't compile.
The first is, the template parameter of a template function can be partially specified:
template<class U, class V> void foo(V v) {}
int main() {
foo<double>(12);
}
This code works, because you specify the first template parameter U and let the compiler determine the second parameter. For the same reason, your VariadicFunc<int>(ConvertsToStruct{}); also requires template argument deduction. Here is a similar example, it compiles:
template<class... U> void bar(U... u) {}
int main() {
bar<int>(12.0, 13.4f);
}
Now we know compiler needs to do deduction for your code, then comes the second part: compiler processes different stages in a fixed order:
cppreference
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
Implicit conversion takes place at overload resolution, after template argument deduction. Thus in your case, the existence of a user-defined conversion operator has no effect when compiler is doing template argument deduction. Obviously ConvertsToStruct itself cannot match anything, thus deduction failed and the code can't compile.
The problem is that with
VariadicFunc<int>(ConvertsToStruct{});
you fix only the first template parameter in the list T....
And the compiler doesn't know how to deduce the remaining.
Even weirder, I can take the address of the function, and then it works
It's because with (&VariadicFunc<int>) you ask for the pointer of the function (without asking the compiler to deduce the types from the argument) so the <int> part fix all template parameters.
When you pass the ConvertToStruct{} part
(&VariadicFunc<int>)(ConvertToStruct{});
the compiler know that T... is int and look if can obtain a Struct<int> from a ConvertToStruct and find the apposite conversion operator.

Overloading of template function in template class

I have a templated operator in templated class and I want to change its behavior for specific type. My code:
#include <iostream>
template <typename N>
struct A {
int x;
template<typename T>
A& operator<<(const T& t) {
x += static_cast<int>(t);
return *this;
}
};
enum class B {
s,t
};
template <typename N>
A<N>& operator<<(A<N>& a, const B& b) {
a.x -= static_cast<int>(b);
return a;
}
int main() {
A<int> a{3};
std::cout << (a<<1).x << " " << (a << B::s).x;
}
g++-4.9 compiles it fine but clang-3.6 complains that it's ambiguous.
Note, that if class is not templated, than both of compilers compile it fine.
What is correct behavior?
Short summary: I believe this is a gcc bug in the template partial ordering rules, and that clang is correct. I filed bug 66914, although it's probably a duplicate of bug 53499 which I didn't notice until afterwards.
In the call
a << B::s;
We have two viable candidates:
template <typename T> A<int>::operator<<(const T& );
template <typename N> operator<<(A<N>&, const B& );
You can rewrite the member function to take a reference to the instance as the first argument, and write out both as instantiatons. So we have:
template <> operator<<(A<int>&, const B& ); // [T = B]
template <> operator<<(A<int>&, const B& ); // [N = int]
Since both are viable candidates, let's go through the rules in [over.match.best] to determine which one is the best viable candidate:
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
Nope, both take the exact same arguments, so the conversion sequences are identical.
— the context is an initialization by user-defined conversion [ ... ]
Nope, irrelevant.
— the context is an initialization by conversion function [ ... ]
Nope, irrelevant.
— F1 is not a function template specialization and F2 is a function template specialization, or, if not that,
Nope, both are function template specializations.
— F1 and F2 are function template specializations, and the function template for F1 is more specialized
than the template for F2 according to the partial ordering rules described in 14.5.6.2.
This is the most complicated of the rules. Ultimately, neither is more specialized than the other. Why? The member function is effectively:
template <typename T> A<int>& operator<<(A<int>&, const T& );
If we synthesized a type for T (call it Unique1), deduction would fail against the free function (since Unique1 wouldn't match B). On the other side, if we synthesized a type for N (call it Unique2), deduction would fail against the member function (since Unique2 wouldn't match int).
Since neither function is more specialized than the other, we've run out of bullet points. The function call should be ambiguous, this is a gcc bug.
This:
template <typename N>
A<N>& operator<<(A<N>& a, const B& b) {
a.x -= static_cast<int>(b);
return a;
}
is not an overload of your member operator (I'm sure there is a more correct way to say it from the standardese point of view), and not a specialization, it is a global template operator that participates in overload resolution.
From that point of view both are equally perfect matches so I think clang is correct.

Overload resolution and universal reference parameters

The following code works and the overloads are found as expected:
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T const& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{});
}
However, if I replace the first overload with a "universal reference" version then it no longer works. The correct overload is not found for NoBuzz.
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T&& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}
What can I do to make it work?
Simple solution
Add an overload that is callable with an rvalue of type NoBuzz.
void foo(NoBuzz const&){ };
void foo(NoBuzz&&) { }; // overload for rvalues
Note: Depending on your actual use case this might not be enough, because if you pass a non-const lvalue type NoBuzz to foo you'd still instantiate the template, since the two NoBuzz overloads doesn't match. At the end of this post is a more complex, but certainly cleaner, solution.
Explanation
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
The problem with your snippet is that your template (A) can be instantiated in such a way that it's a better match than your overload (B).
When the compiler sees that you are trying to call a function named foo with an argument which is an rvalue of type NoBuzz, it will look for all functions named foo taking one argument where a NoBuzz would fit.
Let's say it starts of with your template (A), here it sees that T&& is deducable to any reference type (both lvalue, and rvalue), since we are passing an rvalue T = NoBuzz.
With T = NoBuzz the instantiated template would be semantically equivalent to:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
It will then continue to your overload (B). This overload accepts a const lvalue reference, which can bind to both lvalues and rvalues; but our previous template instantiation (C) can only bind to rvalues.
Since (C) is a better match than (B), binding rvalues to T&& is prefered over U const&, that overload is selected and you get the behavior you describe in your post.
Advanced solution
We can use a technique called SFINAE to conditionally make it impossible to call the template if the type passed doesn't implement .buzz ().
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
The above solution uses a lot of new features of C++11, detailed information is available here:
wikipedia.org - C++11 - auto
wikipedia.org - decltype
Conditional overloading with trailing-return-type possible?
refp's answer explains the behavior you're seeing, and offers a possible solution. Another option is to ensure the foo function template does not make it into the candidate set for overload resolution unless T has a member function named buzz().
template <typename T>
auto foo(T&& t)
-> decltype((void)(t.buzz()), void())
{
t.buzz();
}
After making this change the foo(NoBuzz const&) overload will be selected when you pass it an instance of NoBuzz. Live demo
A detailed explanation of what's going on in the decltype expression in the trailing return type can be found here. The only thing I've done differently here is instead of using three subexpressions, with the middle one being void() to prevent a user defined operator, from being selected, I've cast the result of the first expression to void; the intent and result are identical in both cases.

implicit instantiation of undefined template 'class'

When trying to offer functions for const and non-const template arguments in my library I came across a strange problem. The following source code is a minimal example phenomenon:
#include <iostream>
template<typename some_type>
struct some_meta_class;
template<>
struct some_meta_class<int>
{
typedef void type;
};
template<typename some_type>
struct return_type
{
typedef typename some_meta_class< some_type >::type test;
typedef void type;
};
template<typename type>
typename return_type<type>::type foo( type & in )
{
std::cout << "non-const" << std::endl;
}
template<typename type>
void foo( type const & in )
{
std::cout << "const" << std::endl;
}
int main()
{
int i;
int const & ciref = i;
foo(ciref);
}
I tried to implement a non-const version and a const version for foo but unfortunately this code won't compile on CLANG 3.0 and gcc 4.6.3.
main.cpp:18:22: error: implicit instantiation of undefined template
'some_meta_class'
So for some reason the compiler wants to use the non-const version of foo for a const int-reference. This obviously leads to the error above because there is no implementation for some_meta_class. The strange thing is, that if you do one of the following changes, the code compile well and works:
uncomment/remove the non-const version
uncomemnt/remove the typedef of return_type::test
This example is of course minimalistic and pure academic. In my library I came across this problem because the const and non-const version return different types. I managed this problem by using a helper class which is partially specialized.
But why does the example above result in such strange behaviour? Why doesn't the compiler want to use the non-const version where the const version is valid and and matches better?
The reason is the way function call resolution is performed, together with template argument deduction and substitution.
Firstly, name lookup is performed. This gives you two functions with a matching name foo().
Secondly, type deduction is performed: for each of the template functions with a matching name, the compiler tries to deduce the function template arguments which would yield a viable match. The error you get happens in this phase.
Thirdly, overload resolution enters the game. This is only after type deduction has been performed and the signatures of the viable functions for resolving the call have been determined, which makes sense: the compiler can meaningfully resolve your function call only after it has found out the exact signature of all the candidates.
The fact that you get an error related to the non-const overload is not because the compiler chooses it as a most viable candidate for resolving the call (that would be step 3), but because the compiler produces an error while instantiating its return type to determine its signature, during step 2.
It is not entirely obvious why this results in an error though, because one might expect that SFINAE applies (Substitution Failure Is Not An Error). To clarify this, we might consider a simpler example:
template<typename T> struct X { };
template<typename T> typename X<T>::type f(T&) { } // 1
template<typename T> void f(T const&) { } // 2
int main()
{
int const i = 0;
f(i); // Selects overload 2
}
In this example, SFINAE applies: during step 2, the compiler will deduce T for each of the two overloads above, and try to determine their signatures. In case of overload 1, this results in a substitution failure: X<const int> does not define any type (no typedef in X). However, due to SFINAE, the compiler simply discards it and finds that overload 2 is a viable match. Thus, it picks it.
Let's now change the example slightly in a way that mirrors your example:
template<typename T> struct X { };
template<typename Y>
struct R { typedef typename X<Y>::type type; };
// Notice the small change from X<T> into R<T>!
template<typename T> typename R<T>::type f(T&) { } // 1
template<typename T> void f(T const&) { } // 2
int main()
{
int const i = 0;
f(i); // ERROR! Cannot instantiate R<int const>
}
What has changed is that overload 1 no longer returns X<T>::type, but rather R<T>::type. This is in turn the same as X<T>::type because of the typedef declaration in R, so one might expect it to yield the same result. However, in this case you get a compilation error. Why?
The Standard has the answer (Paragraph 14.8.3/8):
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.
Clearly, the second example (as well as yours) generates an error in a nested context, so SFINAE does not apply. I believe this answers your question.
By the way, it is interesting to notice, that this has changed since C++03, which more generally stated (Paragraph 14.8.2/2):
[...] If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails. [...]
If you are curious about the reasons why things have changed, this paper might give you an idea.

Overloaded function templates with reference parameters

template <typename T> void f(T&) {}
template <typename T> void f(T&&) {}
int main()
{
int x;
f(x); //ambiguous
}
Why is this call ambiguous? The first template specialization is f<int>(int&), and the second is f<int&>(int&). As the parameters are the same, the function template, which is more specialzed according to the partial ordering rules, is better. Then according to Standard 14.8.2.4/9
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
— if the type from the argument template was an lvalue reference and the type from the parameter template was not, the argument type is considered to be more specialized than the other; ...
The first template has T& and the second has T&&, so the first should be more specialized. What is wrong here?
Edit:
This code is tested in g++ 4.6.1 and VC++ 2010 Express, both give the ambiguous error.
Guideline:
Do not overload:
template <typename T> void f(T&) {}
template <typename T> void f(T&&) {}
Reason:
There is a special template deduction rule for the pattern:
template <typename T> void f(T&&) {}
This rule exists in order to enable so called "perfect forwarding". It helps things like bind and make_shared forward their arguments perfectly, preserving both cv-qualifiers and "value category" (lvalue/rvalue-ness).
This special rule says that when f(T&&) is called with an lvalue parameter (e.g. int), that T gets deduced as an lvalue reference (e.g. int&) instead of int. And an rvalue reference to lvalue reference to int collapses down to just lvalue reference to int. I.e.
f(x)
calls
f<int&>(int& && x);
which simplifies to:
f<int&>(int& x);
Edit
This is not more or less specialized than f<int>(int&).
Thanks to Johannes Schaub for the correction (see comments).
Solution:
You can do whatever you want with the single function:
template <typename T> void f(T&&) {}
If T deduces as an lvalue reference, do whatever you wanted to do in your first overload, otherwise do whatever you wanted to do in your second overload:
template <class T> void f_imp(T&, std::true_type) {std::cout << "lvalue\n";}
template <class T> void f_imp(T&&, std::false_type) {std::cout << "rvalue\n";}
template <typename T> void f(T&& x)
{
f_imp(std::forward<T>(x), std::is_lvalue_reference<T>());
}
And use std::forward to perfectly forward x to the implementation-detail function.
Your interpretation of the standard appears to be correct.
template <typename T> void f(T&) {} // #1
template <typename T> void f(T&&) {} // #2
In #1, T is successfully deduced as int, and in #2, T is successfully deduced as int&, so partial ordering is performed to select the function to call. During partial ordering for the call f(x), the types of the first (only, in this case) argument will be ordered ([temp.deduct.partial]/3 bullet 1). During deduction in both directions, type P will be T, and type A will be a synthesized type representing T ([temp.deduct.partial]/5), so deduction succeeds in both directions.
As you observed, [temp.deduct.partial]/9 then applies, and says that #1's first argument is more specialized. Therefore, by [temp.deduct.partial]/10, #1 is selected as the most-specialized template and its specialization is the result of overload resolution.
You didn't mention which compiler you are using. I assume it's g++ -- this appears to be a bug in that compiler (I've tested versions between 4.4.3 and 4.7, and they all reject this code). clang accepts your code, and calls the f(T &) overload as you expected.