Reputation: 660
I was experimenting with SFINAE these days, and something puzzles me. Why my_type_a
cannot be deduced in my_function
's instantiation?
class my_type_a {};
template <typename T>
class my_common_type {
public:
constexpr static const bool valid = false;
};
template <>
class my_common_type<my_type_a> {
public:
constexpr static const bool valid = true;
using type = my_type_a;
};
template <typename T> using my_common_type_t = typename my_common_type<T>::type;
template <typename T, typename V>
void my_function(my_common_type_t<T> my_cvalue, V my_value) {}
int main(void) {
my_function(my_type_a(), 1.0);
}
G++ gives me this:
/home/flisboac/test-template-template-arg-subst.cpp: In function ‘int main()’:
/home/flisboac/test-template-template-arg-subst.cpp:21:30: error: no matching function for call to ‘my_function(my_type_a, double)’
my_function(my_type_a(), 1.0);
^
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: candidate: template<class T, class V> void my_function(my_common_type_t<T>, V)
void my_function(my_common_type_t<T> my_type, V my_value) {}
^~~~~~~~~~~
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: template argument deduction/substitution failed:
/home/flisboac/test-template-template-arg-subst.cpp:21:30: note: couldn't deduce template parameter ‘T’
my_function(my_type_a(), 1.0);
^
What I expected was that, when calling my_function
as I did in main
, T
would be deduced to the type of the function's first argument, and that type would be used in the function's instantiation. But it seems that my_common_type_t<T>
is instantiated before the function, but even then, the type of my_cvalue
would become my_type_a
anyways, so I cannot see why this wouldn't work...
Is there a different way to do this? Should I just avoid two (or more) levels of template indirection?
Upvotes: 0
Views: 120
Reputation: 41770
Well, consider this:
template <>
struct my_common_type<int> {
constexpr static const bool valid = true;
using type = my_type_a;
};
template <>
struct my_common_type<double> {
constexpr static const bool valid = true;
using type = my_type_a;
};
// ...
int main(void) {
my_function(my_type_a{}, 1.0);
}
Does the compiler chooses my_common_type<int>
or my_common_type<double>
?
If the language would permit deduction in you case, it would have to match what T
would be in my_common_type<T>::type
in order to yield the exact type you send to the function parameter. Obviously, it's not only impossible, but with my example above, it may have multiple choices!
Fortunately, there is a way to tell the compiler that my_common_type<T>
will always yield to T
. The basics of the trick is this:
template<typename T>
using test_t = T;
template<typename T>
void call(test_t<T>) {}
int main() {
call(1);
}
What is T
deduces to? int
, easy! The compiler is happy with this kind of match. Also, since test_t
cannot be specialized, test_t<soxething>
is known to only be something
.
Also, this is working too with multiple levels of aliases:
template<typename T>
using test_t = T;
template<typename T>
using test2_t = test_t<T>;
template<typename T>
void call(test2_t<T>) {}
int main() {
call(1); // will also work
}
We can apply this to your case, but we will need some tool:
template<typename T, typename...>
using first_t = T;
This is the same easy match as above, but we can also send some argument that will not be used. We will make sfinae in this unused pack.
Now, rewrite my_common_type_t
to still be an easy match, whilst adding the constraint in the unused pack:
template <typename T>
using my_common_type_t = first_t<T, typename my_common_type<T>::type>;
Note that this is also working:
template <typename T>
using my_common_type_t = first_t<T, std::enable_if_t<my_common_type<T>::valid>>;
Now deduction will happen as expected! Live (GCC) Live (Clang)
Note that this trick will only work with C++14, as sfinae in this case (dropped parameters) is only guaranteed to happen since C++14.
Also note that you should either use struct
for your trait, or use public:
to make the member my_common_type<T>::type
public, or else GCC will output a bogus error.
Upvotes: 2