SFINAE works with deduction but fails with substitution
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}
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.
c++ c++11 templates language-lawyer sfinae
add a comment |
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.
c++ c++11 templates language-lawyer sfinae
Intest(a, b);
,T
cannot be deduced for the second overload oftest
, so this overload is discarded before the instantiation ofWrapper<A>
, and so the list of candidates only contains the firsttest
, which is why this works. In the second case, the second overload is valid candidate sinceT
is explicitly provided, thus implying the instantiation ofWrapper<A>
which fails.
– Holt
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the::type
member type?
– L. F.
2 hours ago
@LF The intent was to restrict the template toWrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.
– rustyx
1 hour ago
add a comment |
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.
c++ c++11 templates language-lawyer sfinae
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.
c++ c++11 templates language-lawyer sfinae
c++ c++11 templates language-lawyer sfinae
asked 3 hours ago
rustyxrustyx
34.2k9104146
34.2k9104146
Intest(a, b);
,T
cannot be deduced for the second overload oftest
, so this overload is discarded before the instantiation ofWrapper<A>
, and so the list of candidates only contains the firsttest
, which is why this works. In the second case, the second overload is valid candidate sinceT
is explicitly provided, thus implying the instantiation ofWrapper<A>
which fails.
– Holt
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the::type
member type?
– L. F.
2 hours ago
@LF The intent was to restrict the template toWrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.
– rustyx
1 hour ago
add a comment |
Intest(a, b);
,T
cannot be deduced for the second overload oftest
, so this overload is discarded before the instantiation ofWrapper<A>
, and so the list of candidates only contains the firsttest
, which is why this works. In the second case, the second overload is valid candidate sinceT
is explicitly provided, thus implying the instantiation ofWrapper<A>
which fails.
– Holt
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the::type
member type?
– L. F.
2 hours ago
@LF The intent was to restrict the template toWrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.
– rustyx
1 hour ago
In
test(a, b);
, T
cannot be deduced for the second overload of test
, so this overload is discarded before the instantiation of Wrapper<A>
, and so the list of candidates only contains the first test
, which is why this works. In the second case, the second overload is valid candidate since T
is explicitly provided, thus implying the instantiation of Wrapper<A>
which fails.– Holt
2 hours ago
In
test(a, b);
, T
cannot be deduced for the second overload of test
, so this overload is discarded before the instantiation of Wrapper<A>
, and so the list of candidates only contains the first test
, which is why this works. In the second case, the second overload is valid candidate since T
is explicitly provided, thus implying the instantiation of Wrapper<A>
which fails.– Holt
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the
::type
member type?– L. F.
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the
::type
member type?– L. F.
2 hours ago
@LF The intent was to restrict the template to
Wrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.– rustyx
1 hour ago
@LF The intent was to restrict the template to
Wrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.– rustyx
1 hour ago
add a comment |
2 Answers
2
active
oldest
votes
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'm not going to do that ...
Hence no Wrapper<T>
is instantiated. The first overload is chosen.
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 becauseA::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.
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
@StoryTeller Well ... given an argument of typeA
,Wrapper<T>
can't be deduced. There is no typeWrapper<T>
even remotely related to an argument of typeA
. Since it is not even deduced, what can be instantiated?
– L. F.
2 hours ago
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includesWrapper<whatever>
iswhatever
can be deduced. Sincewhatever
cannot be deduced, this overload is discarded from the list of candidates, andWrapper<whatever>
is not instantiated.
– Holt
2 hours ago
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
|
show 12 more comments
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)
{ }
MyWrapper
has many more dependencies onT
, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check ifWrapper<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.
– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (usingstd::declval<Wrapper<T>>()
insteadT::type
insidedeclval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.
– max66
2 hours ago
@rustyx You need to hideWrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g.,is_wrapper
... And you need to disable the other overloads whenT
is not a wrapper.
– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
|
show 2 more comments
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55920964%2fsfinae-works-with-deduction-but-fails-with-substitution%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
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'm not going to do that ...
Hence no Wrapper<T>
is instantiated. The first overload is chosen.
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 becauseA::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.
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
@StoryTeller Well ... given an argument of typeA
,Wrapper<T>
can't be deduced. There is no typeWrapper<T>
even remotely related to an argument of typeA
. Since it is not even deduced, what can be instantiated?
– L. F.
2 hours ago
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includesWrapper<whatever>
iswhatever
can be deduced. Sincewhatever
cannot be deduced, this overload is discarded from the list of candidates, andWrapper<whatever>
is not instantiated.
– Holt
2 hours ago
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
|
show 12 more comments
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'm not going to do that ...
Hence no Wrapper<T>
is instantiated. The first overload is chosen.
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 becauseA::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.
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
@StoryTeller Well ... given an argument of typeA
,Wrapper<T>
can't be deduced. There is no typeWrapper<T>
even remotely related to an argument of typeA
. Since it is not even deduced, what can be instantiated?
– L. F.
2 hours ago
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includesWrapper<whatever>
iswhatever
can be deduced. Sincewhatever
cannot be deduced, this overload is discarded from the list of candidates, andWrapper<whatever>
is not instantiated.
– Holt
2 hours ago
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
|
show 12 more comments
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'm not going to do that ...
Hence no Wrapper<T>
is instantiated. The first overload is chosen.
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 becauseA::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.
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'm not going to do that ...
Hence no Wrapper<T>
is instantiated. The first overload is chosen.
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 becauseA::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.
edited 2 hours ago
answered 2 hours ago
L. F.L. F.
1,798626
1,798626
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
@StoryTeller Well ... given an argument of typeA
,Wrapper<T>
can't be deduced. There is no typeWrapper<T>
even remotely related to an argument of typeA
. Since it is not even deduced, what can be instantiated?
– L. F.
2 hours ago
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includesWrapper<whatever>
iswhatever
can be deduced. Sincewhatever
cannot be deduced, this overload is discarded from the list of candidates, andWrapper<whatever>
is not instantiated.
– Holt
2 hours ago
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
|
show 12 more comments
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
@StoryTeller Well ... given an argument of typeA
,Wrapper<T>
can't be deduced. There is no typeWrapper<T>
even remotely related to an argument of typeA
. Since it is not even deduced, what can be instantiated?
– L. F.
2 hours ago
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includesWrapper<whatever>
iswhatever
can be deduced. Sincewhatever
cannot be deduced, this overload is discarded from the list of candidates, andWrapper<whatever>
is not instantiated.
– Holt
2 hours ago
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
2
2
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
@YSC An overload has to be instantiated for its validity to be checked. I will update my answer to clarify on this point.
– L. F.
2 hours ago
2
2
@StoryTeller Well ... given an argument of type
A
, Wrapper<T>
can't be deduced. There is no type Wrapper<T>
even remotely related to an argument of type A
. Since it is not even deduced, what can be instantiated?– L. F.
2 hours ago
@StoryTeller Well ... given an argument of type
A
, Wrapper<T>
can't be deduced. There is no type Wrapper<T>
even remotely related to an argument of type A
. Since it is not even deduced, what can be instantiated?– L. F.
2 hours ago
1
1
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
Sorry, what? I don't recall a non-deduced context being dependent on the argument. It's a property of the parameter.
– StoryTeller
2 hours ago
1
1
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includes
Wrapper<whatever>
is whatever
can be deduced. Since whatever
cannot be deduced, this overload is discarded from the list of candidates, and Wrapper<whatever>
is not instantiated.– Holt
2 hours ago
@StoryTeller "but that includes Wrapper<whatever>" - Well, that includes
Wrapper<whatever>
is whatever
can be deduced. Since whatever
cannot be deduced, this overload is discarded from the list of candidates, and Wrapper<whatever>
is not instantiated.– Holt
2 hours ago
2
2
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
Thanks. What's the standard article reference for "I have to instantiate Wrapper<T> for every possible type T. I'm not going to do that..."
– rustyx
2 hours ago
|
show 12 more comments
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)
{ }
MyWrapper
has many more dependencies onT
, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check ifWrapper<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.
– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (usingstd::declval<Wrapper<T>>()
insteadT::type
insidedeclval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.
– max66
2 hours ago
@rustyx You need to hideWrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g.,is_wrapper
... And you need to disable the other overloads whenT
is not a wrapper.
– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
|
show 2 more comments
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)
{ }
MyWrapper
has many more dependencies onT
, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check ifWrapper<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.
– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (usingstd::declval<Wrapper<T>>()
insteadT::type
insidedeclval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.
– max66
2 hours ago
@rustyx You need to hideWrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g.,is_wrapper
... And you need to disable the other overloads whenT
is not a wrapper.
– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
|
show 2 more comments
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)
{ }
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)
{ }
edited 2 hours ago
answered 3 hours ago
max66max66
40k74575
40k74575
MyWrapper
has many more dependencies onT
, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check ifWrapper<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.
– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (usingstd::declval<Wrapper<T>>()
insteadT::type
insidedeclval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.
– max66
2 hours ago
@rustyx You need to hideWrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g.,is_wrapper
... And you need to disable the other overloads whenT
is not a wrapper.
– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
|
show 2 more comments
MyWrapper
has many more dependencies onT
, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check ifWrapper<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.
– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (usingstd::declval<Wrapper<T>>()
insteadT::type
insidedeclval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.
– max66
2 hours ago
@rustyx You need to hideWrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g.,is_wrapper
... And you need to disable the other overloads whenT
is not a wrapper.
– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
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<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.– rustyx
2 hours ago
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<T>
itself can be instantiated? Besides, would really like to know why it doesn't work.– rustyx
2 hours ago
@rustyx - thinking on it but my first idea (using
std::declval<Wrapper<T>>()
instead T::type
inside declval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.– max66
2 hours ago
@rustyx - thinking on it but my first idea (using
std::declval<Wrapper<T>>()
instead T::type
inside declval()
) gives the same hard error of your case... About the "why?" question, sorry, I'm not an expert.– max66
2 hours ago
@rustyx You need to hide
Wrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g., is_wrapper
... And you need to disable the other overloads when T
is not a wrapper.– Holt
2 hours ago
@rustyx You need to hide
Wrapper<T>
in a template parameter and performs tests on this template parameter using a trait, e.g., is_wrapper
... And you need to disable the other overloads when T
is not a wrapper.– Holt
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@Holt - do you mind if I add an example of your solution? Or do you prefer add an answer yourself?
– max66
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
@max66 Feel free to add the example, here is a non-full tested snippet: godbolt.org/z/Los-fJ
– Holt
2 hours ago
|
show 2 more comments
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55920964%2fsfinae-works-with-deduction-but-fails-with-substitution%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
In
test(a, b);
,T
cannot be deduced for the second overload oftest
, so this overload is discarded before the instantiation ofWrapper<A>
, and so the list of candidates only contains the firsttest
, which is why this works. In the second case, the second overload is valid candidate sinceT
is explicitly provided, thus implying the instantiation ofWrapper<A>
which fails.– Holt
2 hours ago
Just curious: what is your intent here? To select different overloads based on the existence of the
::type
member type?– L. F.
2 hours ago
@LF The intent was to restrict the template to
Wrapper<T>
types, it seemed like it worked like SFINAE sometimes but apparently it didn't.– rustyx
1 hour ago