I would like to better understand why automatic template deduction (applied when compiled with g++ -std=c++17) works in the first three lines in main(), but fails in the fourth. Is there any chance it will be accepted by compilers in the near future?
template <typename P = void>
class A {
public:
void f1() {}
};
template<typename C>
void g() {}
int main() {
A<> a; // works
A aa; // works
g<A<>>(); // works
g<A>(); // fails
return 0;
}
It's just a matter of signature. Basically you're passing the wrong type.
Both A a and A<> a mean you want an instance of A with the default template parameter value, that is, you end up with A< void >.
The function g< C >() accepts a template parameter which happens to be a type, not another templated type. When you invoke it with A<>, you tell the compiler that you want to use "the instantiation" of the templated type A, which is valid. When you invoke it with A you tell the compiler you want to invoke g< C >() with C being a templated type which does not fit its signature.
If you declare/define g() like so template <typename <typename> TTemplatedType> g() it will accepts to be invoked like this g< A >() but g< A<> >() will fail because now it no longer wants something else than a templated type.
With C++17, template argument deduction is also performed when the name of a class template is used as the type of an object being constructed.
Nothing changes for explicit type inside template.
Related
Is there a way to make template deduction work with (implicit) conversion? Like the following example:
template<typename T> struct A {};
template<typename T> struct B
{
B(A<T>); // implicit A->B conversion
};
template<typename... Ts> void fun(B<Ts>...);
int main()
{
A<int> a;
fun(B(a)); // works
fun(a); // does not work (deduction failure)
}
My thoughts:
If A is a subclass of B, everything works. That means that deduction can do implicit conversion using upcasting. So it seems weird that it can not do implicit conversion using a constructor.
Overloading fun for A and B is possible in principle, but for multiple parameters, there are just too many combinations
Adding a deduction guideline (template<typename T> B(A<T>)->B<T>;) does not change anything.
EDIT: some context:
In my actual code, A is a (large) container, and B is a lightweight non-owning view object. The situation is similar to the fact that std::vector<T> can not be implicity converted to std::span<T> during deduction of T, even though for any concrete T, such a conversion exists.
Template argument deduction does not consider any potential type conversions - mainly because deduction happens before any such conversions can happen.
Basically, when the compiler sees fun(a), it first gathers a set of foo functions that are eligible to service that call. If a foo function template is found, the compiler tries to generate a concrete function from it by substituting the template arguments with the types of arguments passed in the call statement. That's where the template argument deduction happens. No type conversion can happen here because there is no concrete type to convert to. In your example, B<T> is not a type to convert to because T is unknown and it cannot be discovered from an argument of type A<int>. It is also not known whether an arbitrary instance of B<T> will be constructible from A<int> because different specializations of B may have different sets of constructors. Hence the deduction fails in your case.
If the deduction succeeds, the concrete function (with the deduced argument types) is added to the candidate set.
When the set of candidates is gathered, then the best matching candidate is chosen. At this point argument conversions are considered. The conversions are possible since the candidates are no longer templates, and the target types for conversions are known.
What you could do to work around it is to allow the compiler to deduce the template as well and then construct B<T> explicitly:
template<typename... Ts> void fun_impl(B<Ts>...);
template<template<typename> class X, typename... Ts>
std::enable_if_t<(std::is_constructible_v<B<Ts>, X<Ts>> && ...)> fun(X<Ts>... args)
{
fun_impl(B<Ts>(args)...);
}
int main()
{
A<int> a;
fun(B(a)); // works, instantiates fun<B, int>
fun(a); // works, instantiates fun<A, int>
}
I think you can provide different overload of func then you come up with and solve your problem of multiple possible conversions.
template<typename T> struct A {};
template<typename T> struct C {};
template<typename T> struct B
{
B(A<T>) {}
B(C<T>) {}
};
template<typename... Ts>
void fun(B<Ts>...)
{
LOGFUN;
}
template<typename... Ts>
void fun(Ts...arg)
{
LOGFUN;
fun(B{arg}...);
}
int main()
{
A<int> a;
C<int> c;
fun(B(a));
fun(a);
fun(c, a, c);
}
https://godbolt.org/z/ne31z8Kfa
I want a setup like the following:
template <typename T> class a {};
class b : public a<int> {};
template <typename T>
void do_foo(std::unique_ptr<a<T>> foo)
{
// Do something with foo
}
int main()
{
do_foo(std::make_unique<b>());
}
This fails to compile with a note saying template argument deduction/substitution failed and mismatched types 'a<T>' and 'b'. It's pretty self-explanatory. I can help the compiler along by writing do_foo<int>(std::make_unique<b>());, but then I'm repeating myself by writing int twice.
Is there a way to get the compiler to deduce the template parameter in this case? And what would you call this behaviour? I tried searching for things like "template type deduction for inherited type", "polymorphic template deduction" etc.
Is there a way to get the compiler to deduce the template parameter in this case?
No. Not in C++14 (or even C++20).
And what would you call this behaviour?
Standard compliant. To be specific, this paragraph applies:
[temp.deduct.call]
4 In general, the deduction process attempts to find template
argument values that will make the deduced A identical to A (after
the type A is transformed as described above). However, there are
three cases that allow a difference:
If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the
transformed A.
The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification
conversion ([conv.qual]).
If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A.
Likewise, if P is a pointer to a class of the form
simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.
This is an exhaustive list of cases where a template argument can be validly deduced from a function argument even if it doesn't match the pattern of the function parameter exactly. The first and second bullets deal with things like
template<class A1> void func(A1&){}
template<class A2> void func(A2*){}
int main() {
const int i = 1;
func(i); // A1 = const int
func(&i); // A2 = const int
}
The third bullet is the one that is closest to our case. A class derived from a template specialization can be used to deduce a template parameter pertaining to its base. Why doesn't it work in your case? Because the function template parameter is unique_ptr<a<T>> and the argument you call it with is unique_ptr<b>. The unique_ptr specializations are not themselves related by inheritance. So they don't match the bullet, and deduction fails.
But it doesn't mean that a wrapper like unique_ptr prevents template argument deduction entirely. For instance:
template <typename> struct A {};
struct B : A<int> {};
template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};
template<typename T>
void do_smth(wrapper<A<T>>) {}
int main() {
do_smth(wrapper<B>{});
}
In this case, wrapper<B> derives from wrapper<A<int>>. So the third bullet is applicable. And by the complex (and recursive) process of template argument deduction, it allows B to match A<T> and deduce T = int.
TL;DR: unique_ptr<T> specializations cannot replicate the behavior of raw pointers. They don't inherit from the specializations of unique_ptr over T's bases. Maybe if reflection ever comes to C++, we'll be able to meta-program a smart pointer that does behave that way.
As workaround, you might add overload:
template <typename T>
void do_foo_impl(a<T>* foo)
{
return do_foo(std::unique_ptr<a<T>>(foo));
}
template <typename T>
auto do_foo(std::unique_ptr<T> foo) -> decltype(do_foo_impl(foo.release()))
{
do_foo_impl(foo.release());
}
Demo
I have the following code
https://godbolt.org/z/7d1arK
#include <memory>
template <typename T>
class A {
};
template <typename T>
class B : public A<T> {};
template <typename T>
void Foo(std::shared_ptr<A<T>> arg){}
int main(){
auto bptr = std::make_shared<B<int>>();
std::shared_ptr<A<int>> aptr = bptr;
// This compiles
Foo(aptr);
// This does not compile
Foo(bptr);
// This compiles
Foo<int>(bptr);
}
My question is why can't the compiler handle the line?
Foo(bptr);
Template argument deduction and user-defined conversions do not mix. Because they are related by a user-defined conversion, shared_ptr<A<int>> and shared_ptr<B<int>> are not "covariant" in the same sense as types like A<int>* and B<int>* for purposes of the C++ language.
Though that's enough to make the call illegal, this case is even harder because there's not a simple user-defined converting constructor or conversion function involved here, but a constructor template:
template <class T> class std::shared_ptr {
public:
template <class Y>
shared_ptr(const shared_ptr<Y>&);
// ...
};
So here the compiler would need to deduce both T and Y to determine T. "Obviously" Y will deduce to int, but the general case of this complication is not practical to solve.
And actually in this case, int is not the only possible value of T. The call Foo<const int>(bptr) would also be legal.
The C++ language specifies exactly when template argument deduction will and won't succeed, and with what resulting template arguments, so that we can be certain code working on one conforming compiler won't fail on another, at least not for this reason. At some point the line must be drawn, and one such line is "user-defined conversions are not considered when deducing template arguments".
As #SamVarshavchik and #aschepler mentioned, template deduction takes place without considering user-defined conversions, which would be necessary to convert from std::shared_ptr<B<int>> to std::shared_ptr<A<int>>. Therefore no suitable instantiation of the template is found and the call fails.
One way to enable this would be to use a wider range of templates, with a static assertion to narrow it down to enabling only actual derived classes of A:
#include <memory>
#include <type_traits>
template <typename T>
class A { };
template <typename T>
class B : public A<T> { };
class C { };
template <template<class> class Derived, class T>
void Foo(std::shared_ptr<Derived<T>> arg) {
static_assert(std::is_base_of_v<A<T>, Derived<T>>);
}
int main() {
auto bptr = std::make_shared<B<int>>();
std::shared_ptr<A<int>> aptr = bptr;
auto cptr = std::make_shared<C>();
// This compiles
Foo(aptr);
// This compiles
Foo(bptr);
// This does not compile
Foo(cptr);
}
In this case, Foo will be able to accept as an argument a std::shared_ptr to anything that inherits from A.
Like #SamVarshavchik said, user-defined implicit conversions are not allowed in template argument deduction. bptr is of type std::shared_ptr<B<int> > which is implicitly convertible to std::shared_ptr<A<int> > since B<int>* is implicitly convertible to A<int>*, but the template expects something like std::shared_ptr<A<T> > directly.
How do I make the following code work properly?
The non-template version compiles perfectly but the template version fails miserably. Why template version fails to figure out which function version to call and how to fix it? I thought about adding to template class AT operator that implicitly converts to BT but it doesn't work either.
class A {};
class B
{
public:
B(A){};
};
void func(B){};
template<typename T>
class AT {};
template<typename T>
class BT
{
public:
BT(AT<T>){};
};
template<typename T>
void funcT(BT<T>){};
int main()
{
func(A{});
funcT(AT<int>{}); // unable to deduce the funcT template argument
funcT<int>(AT<int>{}); // compiles but I don't want to write that
return 0;
}
There are dumb fixes like writing function version that accepts AT<T> and casts it to BT<T>. But I don't want to write a bunch of functions when everything should work as is. I could understand it if it was an ambiguous call...
Implicit conversion won't be considered in template argument deduction:
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
That means, for funcT which expects a BT<T> but passed a AT<int>, T can't be deduced and the fails to be called.
The workaround, as you showed, is to specify template argument explicitly to bypass the template argument deduction.
Non-template functions don't have such issue; they don't require template argument deduction.
The other answer explained what was going on, but there is a way around this
template<class T, template<class>class Temp, typename std::enable_if<std::is_convertible< Temp<T>, BT<T>>::value, bool>::type = true>
void funcT(Temp<T> t) {
auto bt = static_cast<BT<T>>(t);
}
This can be called by fooT(A<int>{}); as well as fooT(B<int>{}); having the same behavior.
what template <class = typename T::type> means? Can you refer me to some blog, specification describing this?
The question originaly came from explanation of sfinae on cpp reference
template <typename A>
struct B { typedef typename A::type type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename B<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo (int);
First, I'll explain typename T::type. This is simply the access of a member type. Here's an example of accessing a member type:
struct foo {
using bar = int;
};
int main() {
foo::bar var{};
// var has type int
}
So why the typename? It simply means we want to access a type. Since we are in a template and T is an unknown type, and foo::bar could also mean accessing a static variable. To disambiguate, we denote that we effectively want to access a type by explicitly typing typename.
Okay, now what does the class = means?
The class = means the same thing as typename =. When declaring template type parameters, we introduce then using class or typename:
template<typename A, typename B>
struct baz {};
But as any parameters in C++, the name is optional. I could have wrote this and the following is completely equivalent:
template<typename, typename>
struct baz {};
Also, you know in function parameters, we can specify default values? Like that:
void func(int a, int b = 42);
int main () {
func(10); // parameter b value is 42
// We are using default value
}
We can also omit parameter names:
void func(int, int = 42);
Just like function parameters, template parameters can have it's name omitted, and can have a default value.
template<typename, typename = float>
struct baz {};
baz<int> b; // second parameter is float
Putting it all together
Now we have this declaration:
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename B<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo (int);
Here we declare a function that takes an int as parameter, and has three template parameter.
The fist parameter is a simple named parameter. The name is T and it's a type template parameter. The second is also a type parameters, but it has no name. However, it has a default value of T::type, which is a member type of T. We are explicitly telling the compiler that T::type must be a member type of T by specifying typename. The third parameter is similar to the second.
This is where SFINAE kicks in: when a default parameter is used, but T::type as as a member type don't exist, how can you assign the second template parameter to that? We can't. If T::type don't exists, we cannot assign the second template parameter. But instead of making it an error, the compiler will simply try another function, because there is a chance another function would be callable.
This is quite similar to simple overloading. You have the f function. It takes a float parameter, an another overload that takes a std::string. Imagine you call f(9.4f). Does the compiler choke because a std::string is not constructible from a float? No! The compiler ain't stupid. It will try another overload, and will find the float version and call it. In SFINAE a similar analogy can be made. The compiler won't stop because there's some overload somewhere that needs an undefined type in a template parameter. It will try another overload.