Reputation: 12289
I'm trying to create a sample class which has different specializations according to whether its template parameter can be constructed with certain parameters. In my sample, with a simple int
. I've tried with:
template<class A_t>
struct creator
{
A_t* ptr = nullptr;
};
template<class A_t>
struct creator<decltype(A_t(int()))>
{
A_t* ptr = new A_t(5);
};
struct A
{
int i;
A(int i) : i(i) {}
};
int main() {
std::cout << creator<A>().ptr << std::endl;
return 0;
}
My intention is that it prints a memory address of a automatically constructed object. However, it is printing a 0. So, it's taking the non-specialized template.
A_t
is deducible with that syntax, among other things because A_t
is given explicitly. Besides, decltype(A_t(int())
has type A_t
(and not A_t&&
for example), a simple test:
std::cout << std::is_same<decltype(A(int()), A>::value << std::endl;
prints 1.
However, that implementation works:
#include <iostream>
template<class A_t, class = A_t>
struct creator
{
A_t* ptr = nullptr;
};
template<class A_t>
struct creator<A_t, decltype(A_t(int()))>
{
A_t* ptr = new A_t(5);
};
struct A
{
int i;
A(int i) : i(i) {}
};
int main() {
std::cout << creator<A>().ptr << std::endl;
return 0;
}
Coliru test with two classes, one accepting an int
as parameter, and other which doesn't.
Why is the first approach not working?
Upvotes: 2
Views: 141
Reputation: 61550
My best gloss of the thinking behind your question is:
For a given instantiating type U
=> A_t
, if the conversion
U(int)
is undefined then the substitution U(int)
=> A_t(int)
will fail in the context decltype(A_t(int()))
and the specialization:
template<class A_t>
struct creator<decltype(A_t(int()))>
{
A_t* ptr = new A_t(5);
};
will be eliminated, leaving only the base template instantiation. But if the
conversion U(int)
is defined that substitution will succeed.
Then, because:
std::is_same<U,decltype(U(int()))>::value == true
both candidates:
// Tweedledum, with of U => A_t
struct creator<U>
{
U* ptr = nullptr;
};
and:
// Tweedledee, with U => decltype(A_t(int()))
struct creator<U>
{
U* ptr = new U(5);
};
are in the running.
Then, the most specialized candidate will be chosen, which will be Tweedledee
,
because satisfying U
=> decltype(A_t(int()))
constrains U
more precisely than
the bare U => A_t
.
This reasoning relies implicitly on it being deducible for Teedledee
that U
= decltype(A_t(int()))
when U
=> A_t
;
which boils down to it being deducible that U(int)
= A_t(int)
in the sole template argument decltype(A_t(int()))
.
You believe that is so and then wonder how gcc can pick Tweedledum
.
As @WhozCraig pointed out, clang++ expressly rejects your deducibility premiss:-
$ clang++-3.8 -Wall -Wextra -pedantic -std=c++14 main.cpp
main.cpp:10:8: warning: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used
struct creator<decltype(A_t(int()))>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:9:16: note: non-deducible template parameter 'A_t'
template<class A_t>
^
And it turns out that gcc has been induced into a diagnostic lapse here by
the unlucky phrasing of your SFINAE locution, decltype(A_t(int()))
If you replace that with decltype(A_t{int()})
, then gcc also gets with the program:
$ g++-6 -Wall -Wextra -pedantic -std=c++14 main.cpp
main.cpp:10:8: error: template parameters not deducible in partial specialization:
struct creator<decltype(A_t{int()})>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:10:8: note: ‘A_t’
After that, clang++ is the only compiler of the three that will
compile the program at all - and instantiates creator<A>
from the base template.
This eventual consensus that A_t
is not deducible in decltype(A_t{int()})
is endorsed by the C++14 Standard:
Deducing template arguments from a type [temp.deduct.type]
1 Template arguments can be deduced in several different contexts, but in each case a type that is specified in terms of template parameters (call it P) is compared with an actual type (call it A), and an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.
...
4 In most cases, the types, templates, and non-type values that are used to compose P participate in template argument deduction... In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
5 The non-deduced contexts are:
...
(5.2) - The expression of a decltype-specifier.
...
Para 4 also explains why your second approach succeeds.
Upvotes: 1