Could you help me understand why the argument deduction works for the class template and does not work for the function template?
If I understand correctly, the class template defines a function, so when I call it is possible for the compiler to make an implicit cast, but in case of the function template, there is no function definition at the moment, so implicit cast not happening.
But I don't understand why the compiler can't create function definition and then apply implicit cast?
#include <functional>
template<typename ...ARGS>
class Test1
{
public:
void add(const std::function<void(ARGS...)>&) {}
};
class Test2
{
public:
template<typename ...ARGS>
void add(const std::function<void(ARGS...)>&) {}
};
void func(int) {}
int main()
{
Test1<int> test1;
test1.add(func);
Test2 test2;
test2.add<int>(func);
}
The error is:
In function 'int main()':
25:24: error: no matching function for call to 'Test2::add(void (&)(int))'
25:24: note: candidate is:
14:10: note: template void Test2::add(const std::function&)
14:10: note: template argument deduction/substitution failed:
25:24: note: mismatched types 'const std::function' and 'void(int)'
In the first case, you are explicitly instantiating the class template Test1. This means the function declaration for its add member is generated with the signature add(const std::function<void(int)>&). When the compiler subsequently tries to resolve test1.add(func), there is only that one candidate. Since std::function<void(int)> can be implicitly constructed from a void(int) function, the signatures match, the compiler just instantiates the member function definition and everything is good.
In the second case, the compiler has to perform template argument deduction/substitution to see if it can "use" the add template. You may think that specifying int would nail down the template parameters so that no deduction is necessary, but that is not the case: It could be that you mean to partially specify template arguments, see for example here. In other words, you might be trying to instantiate the function template with more parameters than you specified explicitly, at least the compiler doesn't know if you do. So it still has to try and match the types of std::function<void(ARGS...)> (or more precisely, std::function<void(int, ...)> and void(int), which it can't, because implicit conversions are not considered for the deduction.
In short: Specifying explicit template arguments does not prevent template parameter deduction for variadic function templates.
Note: I am not 100% firm with the exact terminology, any language lawyering to correct me is appreciated!
Edit: I am basing this primarily on what I read here.
My original reasoning for why the following snippets work was wrong, but as #NathanOliver helped me out (see below), here is the revised explanation: During template argument deduction, no type conversions are performed. Passing a function pointer to a function that takes a std::function argument requires such a conversion. To circumvent this issue, you can call the method like this
test2.add(std::function<void(int)>(func));
or adjust the definition of Test2 to
class Test2
{
template<typename ...ARGS>
void add(void(*)(ARGS...)) {}
}
which works together with the original call
test2.add<int>(func);
In both examples, no conversion is necessary. The call to Test1::add worked out because the template type deduction has been performed before calling the method, hence the conversion could take place.
Note also that the same issue arises when Test2 is declared with one single template parameter,
class Test2
{
template<typename T>
void add(const std::function<void(T)>&) {}
}
with the following uses cases on the caller's side:
test2.add<int>(func); // Conversion ok, function template specified
test2.add(std::function<void(int)>(func)); // Type deduction, no conversion
test2.add(func); // Error, conversion AND type deduction
Related
I am very new to c++ and am trying to understand how "generic" types are "enforced" in templates, specifically with something like charT.
After reading this question I understand that charT can be any char-like object, but I am wondering what the appropriate way is to check that the user actually supplied a valid charT.
In other words, what happens if you provide a double instead? Do you just rely on the complier throwing an error when the function inevitably tries to do something invalid with the double? Or is there a formal way to check that the supplied type is valid?
Generally, this is can be done in C++ using type traits, std::enable_if and SFINAE (Substitution Failure Is Not An Error), assuming that you're using pre-C++20 code. The basic principle is to check if a type has a certain property and if it doesn't, to disable a function overload or class specialization.
For example, if you want to write a function template that only makes sense for classes derived from a certain base class, you can write:
#include <type_traits>
struct Base {};
struct Derived : public Base {};
template<typename T, typename = std::enable_if_t<std::is_base_of_v<Base, T>>>
void foo(const T&) {
}
int main() {
Derived d{};
foo(d);
//foo(1); <-- ERROR: requirement not fulfilled
return 0;
}
Here, a second, unnamed template parameter is used and assigned to the type "returned" by std::enable_if_t (this is shorthand for typename std::enable_if<...>::type to save one from typing out all that and is available as of C++14). std::enable_if will only have the type definition called type if the first parameter evaluates to true. In this example, we check if std::is_base_of_v<Base, T> is true. If that is the case, then std::enable_if will be instantiated to have a using type = void; declaration (if one desires different type than void, this can be passed to std::enable_if as a second parameter right after the bool parameter). Therefore, the unnamed parameter will be set to void and the program compiles. If, however, the first parameter evaluates to false, there will be no type definition in std::enable_if, and the function template will not be instantiated, because the type for the second parameter in our template cannot be substituted, which will merely remove the function from the overload set and not cause a compiler error. This is what SFINAE means. Of course if no other viable overload can be found, you will still get an error, but not due to the fact that the template instantiation failed.
If you uncomment the call to foo(1), clang, for instance, will say:
<source>:15:5: error: no matching function for call to 'foo'
foo(1);
^~~
<source>:8:6: note: candidate template ignored: requirement 'std::is_base_of_v<Base, int>' was not satisfied [with T = int]
void foo(const T&) {
This, of course, is much more readable that some page-long, cryptic error message arising from an error from within the function caused by some operation that simply cannot be called on an int.
Using C++20 concepts, you could also write the template like so:
template<typename T>
requires std::is_base_of_v<Base, T>
void foo(const T&) {
}
Concepts greatly improve readability as template metaprogramming code can quickly get very lenghty and hard to read.
If you need to check more complex properties of a type, you can combine multiple type traits or write your own, which can get very complicated depending on what it is that you need to check.
I hope that clears things up a little bit for you!
#include <iostream>
template<typename T>
void test(T,typename T::type){ //#1
}
template<typename T,typename U>
void test(T,U){ //#2
}
int main(){
test(0,0); //we know #2 is called
}
consider above code,to call function test for arguments 0,0,the complier need to find the best match overload for these arguments and instantiate the function template,firstly,there are two function template named test,the complier deduce the template paraments from arguments for them. I want to know the instantiation or the substitution which is first performanced on the function template in the next step?whether the overload resolution is performanced after them?
when to instantiate function template
If you call the name of an overloaded function template, the compiler will try to deduce its template arguments and check its explicitly declared template arguments. If successful, it will instantiate a function template specialization, then add this specialization to the set of candidate functions used in overload resolution
when to perform substitution
Specifically, when creating a candidate set for overload resolution, some (or all) candidates of that set may be the result of instantiated templates with (potentially deduced) template arguments substituted for the corresponding template parameters. If an error occurs during the substitution of a set of arguments for any given template, the compiler removes the potential overload from the candidate set instead of stopping with a compilation error, provided the substitution error is one the C++ standard grants such treatment
I have not found the document about the order of substitution and instantiation
UPDATE:
the deduction,substitution(the sfinae is happened here) is parts of instantiation,however,except these,generate defination by substituting template arguments at the point of instantiation also a part of instantiation,when does it happen?after overload resolution or after the substitution(here is the sfinae)?
Your example doesn't show much regarding your question as there is only one viable candidate: Substitution fails for #1 as int::type is ill-formed, and thus there is only one function, #2.
On the other hand, it does mean that parameter substitution has to happen as part of the instantiation (as noted by #IgorTandetnik). And, for there to be any overloads to resolve, template instantiation has to happen before.
If you change #1 to
template <typename T>
void test(T,T){}
you would have two viable overloads, and #1 would be selected.
Edit:
On the other hand, if you change your main function to
struct {
using type = int;
int val = 0;
} x;
test(x,0);
both templates will be instantiated but overload resolution will fail as the two functions void test(anonymous struct, int) are ambiguous.
That also illustrates that the templates must be instantiated before overload resolution can take place.
Consider the following MCVE
struct A {};
template<class T>
void test(T, T) {
}
template<class T>
class Wrapper {
using type = typename T::type;
};
template<class T>
void test(Wrapper<T>, Wrapper<T>) {
}
int main() {
A a, b;
test(a, b); // works
test<A>(a, b); // doesn't work
return 0;
}
Here test(a, b); works and test<A>(a, b); fails with:
<source>:11:30: error: no type named 'type' in 'A'
using type = typename T::type;
~~~~~~~~~~~~^~~~
<source>:23:13: note: in instantiation of template class 'Wrap<A>' requested here
test<A>(a, b); // doesn't work
^
<source>:23:5: note: while substituting deduced template arguments into function template 'test' [with T = A]
test<A>(a, b); // doesn't work
LIVE DEMO
Question: Why is that? Shouldn't SFINAE work during substitution? Yet here it seems to work during deduction only.
Self introduction
Hello everyone, I am an innocent compiler.
The first call
test(a, b); // works
In this call, the argument type is A. Let me first consider the first overload:
template <class T>
void test(T, T);
Easy. T = A.
Now consider the second:
template <class T>
void test(Wrapper<T>, Wrapper<T>);
Hmm ... what? Wrapper<T> for A? I have to instantiate Wrapper<T> for every possible type T in the world just to make sure that a parameter of type Wrapper<T>, which might be specialized, can't be initialized with an argument of type A? Well ... I don't think I'm going to do that ...
Hence I will not instantiate any Wrapper<T>. I will choose the first overload.
The second call
test<A>(a, b); // doesn't work
test<A>? Aha, I don't have to do deduction. Let me just check the two overloads.
template <class T>
void test(T, T);
T = A. Now substitute — the signature is (A, A). Perfect.
template <class T>
void test(Wrapper<T>, Wrapper<T>);
T = A. Now subst ... Wait, I never instantiated Wrapper<A>? I can't substitute then. How can I know whether this would be a viable overload for the call? Well, I have to instantiate it first. (instantiating) Wait ...
using type = typename T::type;
A::type? Error!
Back to L. F.
Hello everyone, I am L. F. Let's review what the compiler has done.
Was the compiler innocent enough? Did he (she?) conform to the standard?
#YSC has pointed out that [temp.over]/1 says:
When a call to the name of a function or function template is written
(explicitly, or implicitly using the operator notation), template
argument deduction ([temp.deduct]) and checking of any explicit
template arguments ([temp.arg]) are performed for each function
template to find the template argument values (if any) that can be
used with that function template to instantiate a function template
specialization that can be invoked with the call arguments. For each
function template, if the argument deduction and checking succeeds,
the template-arguments (deduced and/or explicit) are used to
synthesize the declaration of a single function template
specialization which is added to the candidate functions set to be
used in overload resolution. If, for a given function template,
argument deduction fails or the synthesized function template
specialization would be ill-formed, no such function is added to the
set of candidate functions for that template. The complete set of
candidate functions includes all the synthesized declarations and all
of the non-template overloaded functions of the same name. The
synthesized declarations are treated like any other functions in the
remainder of overload resolution, except as explicitly noted in
[over.match.best].
The missing type leads to a hard error. Read https://stackoverflow.com/a/15261234. Basically, we have two stages when determining whether template<class T> void test(Wrapper<T>, Wrapper<T>) is the desired overload:
Instantiation. In this case, we (fully) instantiate Wrapper<A>. In this stage, using type = typename T::type; is problematic because A::type is nonexistent. Problems that occur in this stage are hard errors.
Substitution. Since the first stage already fails, this stage is not even reached in this case. Problems that occur in this stage are subject to SFINAE.
So yeah, the innocent compiler has done the right thing.
I'm not a language lawyer but I don't think that defining a using type = typename T::type; inside a class is, itself, usable as SFINAE to enable/disable a function receiving an object of that class.
If you want a solution, you can apply SFINAE to the Wrapper version as follows
template<class T>
auto test(Wrapper<T>, Wrapper<T>)
-> decltype( T::type, void() )
{ }
This way, this test() function is enabled only for T types with a type type defined inside it.
In your version, is enabled for every T type but gives error when T is incompatible with Wrapper.
-- EDIT --
The OP precises and asks
My Wrapper has many more dependencies on T, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check if Wrapper itself can be instantiated?
As suggested by Holt, you can create a custom type traits to see if a type is a Wrapper<something> type; by example
template <typename>
struct is_wrapper : public std::false_type
{ };
template <typename T>
struct is_wrapper<Wrapper<T>> : public std::true_type
{ using type = T; };
Then you can modify the Wrapper version to receive a U type and check if U is a Wrapper<something> type
template <typename U>
std::enable_if_t<is_wrapper<U>{}> test (U, U)
{ using T = typename is_wrapper<U>::type; }
Observe that you can recover the original T type (if you need it) using the type definition inside the is_wrapper struct.
If you need a non-Wrapper version of test(), with this solution you have to explicity disable it when T is a Wrapper<something> type to avoid collision
template <typename T>
std::enable_if_t<!is_wrapper<T>{}> test(T, T)
{ }
Deduction of the function called in a function call expression is performed in two steps:
Determination of the set of viable functions;
Determination of the best viable function.
The set of viable function can only contain function declaration and template function specialization declaration.
So when a call expression (test(a,b) or test<A>(a,b)) names a template function, it is necessary to determine all template arguments: this is called template argument deduction. This is performed in three steps [temp.deduct]:
Subsitution of explicitly provided template arguments (in names<A>(x,y) A is explicitly provided);(substitution means that in the function template delcaration, the template parameter are replaced by their argument)
Deduction of template arguments that are not provided;
Substitution of deduced template argument.
Call expression test(a,b)
There are no explictly provided template argument.
T is deduced to A for the first template function, deduction fails for the second template function [temp.deduct.type]/8. So the second template function will not participate to overload resolution
A is subsituted in the declaration of the first template function. The subsitution succeeds.
So there is only one overload in the set and it is selected by overload resolution.
Call expression test<A>(a,b)
(Edit after the pertinent remarks of #T.C. and #geza)
The template argument is provided: A and it is substituted in the declaration of the two template functions. This substitution only involves the instantiation of the declaration of the function template specialization. So it is fine for the two template
No deduction of template argument
No substitution of deduced template argument.
So the two template specializations, test<A>(A,A) and test<A>(Wrapper<A>,Wrapper<A>), participate in overload resolution. First the compiler must determine which function are viable. To do that the compiler needs to find an implicit conversion sequence that converts the function argument to the function parameter type [over.match.viable]/4:
Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F.
For the second overload, in order to find a conversion to Wrapper<A> the compiler needs the definition of this class. So it (implicitly) instantiates it. This is this instantiation that causes the observed error generated by compilers.
Considering the following small code snippet:
template <typename T>
class A{
public:
A() { };
~A() { };
// ...
template <typename outT = T> operator std::vector<outT>(){ /* implicit/explicit cast to std::vector */ }
};
template <typename T = float>
void myFunc(const std::vector<T>& _arg){
printf("myFunc\n");
}
int main(int argc, char const *argv[]) {
A<int> foo;
myFunc(foo);
}
When trying to compile, I get the error
template argument deduction/substitution failed: [...] ‘A<int>’ is not derived from ‘const std::vector<T>’
While, on the other hand, if myFunc is not a template, it will compile and run just fine. I assume the error is caused by the fact that since myFunc accepts multiple vector types as argument, the compiler does not know to what type it should convert foo to. However, shouldn't the default template values be used as fallback in such case? Is there an alternative way to be able to pass an object of class A to myFunc?
Implicit conversions are not considered when attempting to deduce template parameters. Since A<int> cannot match const std::vector<T>, the myFunc template is not a viable candidate. This remains true even if you explicitly instantiate it beforehand (and even if the conversion operator is not templated).
For overload resolution, implicit conversion are considered, which is why the non-templated version works. The templated version never gets to participate because it is not a viable candidate.
The problem is that template argument deduction does not consider implicit conversions, so when you write myFunc(foo) the compiler is unable to determine a type for T. And that's really the end of the story.
See Template argument deduction on C++ reference.
Walter E. Brown gave a fantastic talk about how function templates work (including why you should call them function templates and not templatized functions) at CppCon2018 which I highly recommend.
See "C++ Function Templates: How Do They Really Work?" on youtube.
Say you have two structures, Generic_A and Generic_B. Generic_B is derived from Generic_A. Why is it that when Generic_B tries to access a method in its parent, Generic_A, it generates the following error:
test2.cpp: In function 'int main()':
test2.cpp:26: error: no matching function for call to 'case1(void (Generic_A::*)()'
This code, compiled using gcc version 4.4.6, replicates the problem:
#include <stdio.h>
struct Generic_A
{
void p1() { printf("%s\n", __PRETTY_FUNCTION__); };
};
struct Generic_B : public Generic_A
{
void p2() { printf("%s\n", __PRETTY_FUNCTION__); };
};
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
template <class T>
void case2( void (T::*p)() ) {
printf("%s\n", __PRETTY_FUNCTION__);
}
main()
{
//generates error
case1<Generic_B>(&Generic_B::p1);
//compiles fine
case2<Generic_B>(&Generic_B::p1);
}
The only apparent difference between the two function calls is that case1() has a template argument parameter, and case2() doesn't. Shouldn't they both allow you to pass a function pointer to a method in Generic_B's parent (ie &Generic_B::p1)?
Also, casting the function pointer in case1 seems to sometimes resolve the error:
case1<Generic_B>( (void (Generic_B::*)()) &Generic_B::p1);
What is going on?
This is tricky, but it turns out g++ is correct.
First, the type of expression &Generic_B::p1 is void (Generic_A::*)(). The compiler uses Generic_B:: to qualify its name lookup and finds the member of Generic_A. The expression type depends on the definition of the member found, not the type used within the qualified-id.
But it's also legal to have
void (Generic_B::*member)() = &Generic_B::p1;
since there is an implicit conversion from void (Generic_A::*)() to void (Generic_B::*)().
Whenever a function template is used as a function call, the compiler goes through three basic steps (or attempts to):
Substitute any explicit template arguments for the template parameters in the function declaration.
For each function parameter that still involves at least one template parameter, compare the corresponding function argument to that function parameter, to (possibly) deduce those template parameters.
Substitute the deduced template parameters into the function declaration.
In this case, we have the function template declaration
template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );
where the template parameters are T and ARGS, and the function call expression
case1<Generic_B>(&Generic_B::p1)
where the explicit template argument is Generic_B and the function argument is &Generic_B::p1.
So step 1, substitute the explicit template argument:
void case1( void (Generic_B::*p)(ARGS...) );
Step 2, compare parameter types and argument types:
The parameter type (P in Standard section 14.8.2) is void (Generic_B::*)(ARGS...). The argument type (A) is void (Generic_A::*)().
C++ Standard (N3485) 14.8.2.1p4:
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 (4.4).
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.
So type deduction allows for certain implicit conversions involving const / volatile and/or derived-to-base conversions, but implicit conversions of pointers to members are not considered.
In the case1 example, type deduction fails, and the function is not a match.
Unfortunately, there's no way to explicitly specify that your template parameter pack ARGS should be substituted with an empty list. As you already discovered, you can get this working by explicitly doing the necessary pointer to member function conversion yourself, even though it's otherwise valid as an implicit conversion.