3

In the next code, the template argument deduction fails when I try to pass a std::tuple of reference derived classes as argument to the function that receive a std::tuple of reference base classes. Why the compiler can not be deduced the template arguments T1 and T2? and How can I fix it?

// Example program
#include <iostream>
#include <tuple>

template<typename T>
struct Base {};

template<typename T>
struct Derived1 : Base<T> {};

template<typename T>
struct Derived2 : Base<T> {};

template<typename T1, typename T2>
void function(std::tuple<Base<T1>&,Base<T2>&> arg)
{
    std::cout << "Hello\n";
}

int main()
{
    Derived1<int> d1;
    Derived2<double> d2;

    //function(std::tie(d1, d2));  /* In this case the template argument deduction/substitution failed */
    function<int,double>(std::tie(d1, d2)); /* here works */

    Base<int>& b1 = d1;
    Base<double>& b2 = d2;

    function(std::tie(b1, b2)); /* but, in this case also works */
}

This is the compile error for the line code function(std::tie(d1, d2));:

 In function 'int main()':
25:30: error: no matching function for call to 'function(std::tuple<Derived1<int>&, Derived2<double>&>)' 
25:30: note: candidate is: 
15:6: note: template<class T1, class T2> void function(std::tuple<Base<T>&, Base<T2>&>) 
15:6: note: template argument deduction/substitution failed: 
25:30: note: mismatched types 'Base<T>' and 'Derived1<int>' 
25:30: note: 'std::tuple<Derived1<int>&, Derived2<double>&>' is not derived from 'std::tuple<Base<T>&, Base<T2>&>' 
Solrac
  • 58
  • 8
  • The answer is basically that deduction dislikes conversions; it's not happy deducing `Base &` for an argument of `Double &`. I'll let someone more fluent in standardese draft an actual answer. – cdhowie Sep 13 '17 at 04:21
  • `std::tuple` is a class that's distinct and unrelated to std::tuple. There is no implicit conversion between the two. There's a user-defined conversion, but those are not considered during template argument deduction. – Igor Tandetnik Sep 13 '17 at 04:41
  • @cdhowie It would actually be fine to deduce `Base&` from an lvalue argument of type `Derived1`, since when a function argument has class type, base classes of the argument type are also considered if the direct deduction fails. But the trouble here is this only applies to the argument type itself, no other contexts like a template parameter/argument used inside `std::tuple`. Or looked at another way, the relation between the two `tuple` types is a user-defined conversion, and not a derived-to-base relation. – aschepler Sep 13 '17 at 11:28

1 Answers1

1

Deduction doesn't work this way. It pulls in before any conversion or whatever. Here the compiler expects a Base<T> from which to deduce T and you are trying to pass a DerivedN<T>. They are completely different beasts from the point of view of the type system and the function is discarded when trying to find a good match for the call.
Look at the error, it's quite clear.

How can I fix it?

You can use something like this to have them accepted and still force the fact that they derive from Base:

#include<type_traits>

// ...

template<template<typename> class C1, template<typename> class C2, typename T1, typename T2>
std::enable_if_t<(std::is_base_of<Base<T1>, C1<T1>>::value and std::is_base_of<Base<T2>, C2<T2>>::value)>
function(std::tuple<C1<T1>&, C2<T2>&> arg)
{
    std::cout << "Hello\n";
}

// ...

See it up and running on wandbox.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • nice workaround, and this can be expanded to parameter pack using [this answer](https://stackoverflow.com/a/13563429/5631903) for `std::is_base_of<>` whit pack expansion. [Look the code here](http://cpp.sh/6otoi) – Solrac Sep 13 '17 at 15:11
  • one more thing, `std::enable_if_t<>` is a helper for c++14 and above; for c++11 we can to use: `typename std::enable_if<>::type` :) – Solrac Sep 13 '17 at 15:31