0

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?

Flávio Lisbôa
  • 651
  • 5
  • 19
  • In `my_common_type::type`, `T` is in [non-deduced context](http://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts). You expect the compiler to instantiate `my_common_type` with every possible type `T`, in hopes that, for exactly one of them, `my_common_type::type` comes out to be compatible with `my_type_a`; or else engage in theorem proving exercise to try and find such a type analytically. The compiler does neither. – Igor Tandetnik Jul 31 '17 at 02:35
  • @Igor I understand that the Rule 1 (from the link you provided) certainly matches my example. However, why is it not clear that `T` is not necessarily `my_type_a`? If `my_common_type` was instantiated before instantiating `my_function`, the type would be either `my_type_a` or nothing (and therefore the function would be eliminated through SFINAE). If it was instead instantiated during or after, the compiler would have the information of `my_common_type` as a candidate (and because of that, `T = my_type_a`), would it not? – Flávio Lisbôa Jul 31 '17 at 02:59
  • *"why is it not clear"* That's the theorem-proving exercise I was talking about. "Clear" here means "can be proven from available facts". And perhaps it can be - but the compiler is not required to possess this kind of reasoning engine. – Igor Tandetnik Jul 31 '17 at 03:04
  • I thought that information could be obtained from the call site. But perhaps I'm considering things from the wrong perspective. I think I understood the problem here, but I'm having a hard time trying to come up with a clear answer. `my_function` receives a value of type `my_common_type::type` in argument `my_cvalue`, and `T` is not used anywhere else. What I'm passing to the function is a value of `my_type_a`, that is concrete. `my_common_type::type` is still unknown, because `::type` depends on the template substitution, that in turn depends on `T`, that is yet not known. – Flávio Lisbôa Jul 31 '17 at 03:29

1 Answers1

2

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.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Wow, that's certainly a good option, and it answers my question! But my case is a bit more complicated that that, because `my_common_type::type` is selected depending on the template parameters. In other words, it's not just a matter of enabling. Also, I thought the compiler would first look at the function call site and see that `T` is `my_type_a`, regardless of `my_common_type`'s specializations. Also also, I think neither `my_common_type` nor `my_common_type` would be selected because none of them are specializations of `my_common_type` for `T = my_type_a`. – Flávio Lisbôa Jul 31 '17 at 03:16
  • 1
    I wanted to update my question, but your answer is so useful that I feel like making another question to answer my specific problem. – Flávio Lisbôa Jul 31 '17 at 03:21
  • @FlávioLisbôa Thanks! Always happy to help. Asking another question would be the proper way to do it in this site indeed. – Guillaume Racicot Jul 31 '17 at 04:13