简体   繁体   中英

Why can't the compiler deduce the return type if argument type depends on a template parameter

I am getting a template deduction error when trying to choose a function of an overload set ( foo ) within a template based on another template parameter:

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int";
}

void foo(double a) {
    std::cout << "double";
}

template <typename T, typename R>
void print(const T& data, R(*fun)(typename T::type) ) {
    fun(data.value);   
}

struct IntData{
    using type = int;
    type value;
};

int main()
{
  IntData x{1};
  print(x, foo);
}

I get the following error:

In function 'int main()':
27:15: error: no matching function for call to 'print(IntData&, <unresolved overloaded function type>)'
27:15: note: candidate is:
15:6: note: template<class T, class R> void print(const T&, R (*)(typename T::type))
15:6: note:   template argument deduction/substitution failed:
27:15: note:   couldn't deduce template parameter 'R'

As template deduction should proceed from left to right, my assumption was that once the type of T is deduced, the type of R should also be deducable. Actually, I can get rid of the error when calling print like

print<IntData>(x, foo);

which seems to show that R can actually be deduced once T is known. So why doesn't it work if both parameter should be deduced?

Thank you!

I believe it is because you have R as the return type for your function pointer argument.

Note this quote from a previous question

So, when we ask about the signature of a function, we have to give two answers:

For functions that are specializations of function templates , the signature includes the return type .

For functions that are not specializations , the return type is not part of the signature .

Since foo is simply an overloaded function and void is not part of the foo function signature, R will not assist the compiler in deducing correct function overload. Therefore, the use of foo as a function pointer is ambiguous within the scope of main. The compiler usually resolves the overload by matching the types of the provided arguments, for which there are none when the function pointer is by itself.

I believe this is the most robust solution, to include an intermediary function to resolve the previously ambiguous function pointer. I included some other types in addition to int to demonstrate the flexibility of using auto with the strategies mentioned below.

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int" << std::endl;
}

void foo(double a) {
    std::cout << "double" << std::endl;
}

bool foo(char a) {
    std::cout << "char" << std::endl;
    return true;
}

template <typename T, typename R>
R print(const T& data, R(*fun)(typename T::type) ) {
    return fun(data.value);   
}

struct IntData{
    using type = int;
    type value;
};
struct DoubleData{
    using type = double;
    type value;
};
struct CharData{
    using type = char;
    type value;
};
template <typename T>
auto print2(const T& data)
{
  auto(*fooResolved)(typename T::type) = foo;
  return print(data,fooResolved);
}

int main()
{
  IntData x{1};
  print2(x);

  DoubleData y{1.0};
  print2(y);

  CharData z{'a'};
  bool result = false;
  std::cout << "bool before: " << result << std::endl;
  result = print2(z);
  std::cout << "bool after : " << result << std::endl;
}

Here are a few more potential solutions to help illustrate the problem:

(note the change is removing R as the second template argument)

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int";
}

void foo(double a) {
    std::cout << "double";
}

template <typename T>
void print(const T& data, void(*fun)(typename T::type) ) {
    fun(data.value);   
}

struct IntData{
    using type = int;
    type value;
};

int main()
{
  IntData x{1};
  print(x, foo);
}

As well as this (passing the value directly, which allows for multiple return types)

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int";
}

void foo(double a) {
    std::cout << "double";
}

template <typename T, typename R>
void print(const T& data, R (*fun)(T) ) {
    fun(data);   
}

struct IntData{
    using type = int;
    type value;
};

int main()
{
  IntData x{1};
  print(x.value, foo);
}

And to further illustrate the original issue (see the return type is now deduced)

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int" << std::endl;
}

bool foo(double a) {
    std::cout << "double" << std::endl;
    return true;
}

template <typename T, typename R>
R print(const T& data, R (*fun)(T) ) {
    return fun(data);   
}

struct IntData{
    using type = int;
    type value;
};
struct DoubleData{
    using type = double;
    type value;
};

int main()
{

  IntData x{1};
  print(x.value, foo);

  //foo(int) does not return a value
  //bool test = print(x.value, foo); // Does not compile

  DoubleData y{1.0};

  bool result = false;
  result = print(y.value, foo);
  std::cout << result << std::endl;

}

And while we're at it, you could also resolve them given the original code by explicitly specifying which foo you want

#include <iostream>
#include <string>

void foo(int a){
    std::cout << "int";
}

void foo(double a) {
    std::cout << "double";
}

template <typename T, typename R>
void print(const T& data, R(*fun)(typename T::type) ) {
    fun(data.value);   
}

struct IntData{
    using type = int;
    type value;
};

int main()
{
  IntData x{1};
  void(*fooResolved)(int) = foo;
  print(x, fooResolved);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM