Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging template deduction failure

Tags:

c++

templates

Why doesn't the following work:

std::string fn(int i)
{
        sleep(i); // number of seconds to sleep for
        std::cout << "Exiting after pausing " << i << " seconds\n";
        return std::to_string(i+1);
}

template<typename T, typename U>
int simult( std::function<T(U)> f, std::vector<U> args)
{
        return 666;
}


int main()
{

        std::vector<int> args { 6, 5, 7 };        
        int r = simult(fn, args);
}

Function simult obviously isn't complete: I'm just trying to get something to compile at this stage. The idea is that you supply a function and a list of args, and simult will apply the args piecewise to f using threaded calls.

At this stage, I'm just trying to figure out the compilation problem.

simult.cc: In function ‘int main()’:
simult.cc:43:25: error: no matching function for call to ‘simult(std::__cxx11::string (&)(int), std::vector<int>&)’
  int r = simult(fn, args);
                         ^
simult.cc:23:5: note: candidate: template<class T, class U> int simult(std::function<T(U)>, std::vector<U>)
 int simult( std::function<T(U)> f, std::vector<U> args)
     ^~~~~~
simult.cc:23:5: note:   template argument deduction/substitution failed:
simult.cc:43:25: note:   mismatched types ‘std::function<T(U)>’ and ‘std::__cxx11::string (*)(int) {aka std::__cxx11::basic_string<char> (*)(int)}’
  int r = simult(fn, args);
                         ^
like image 801
blippy Avatar asked Jun 09 '16 13:06

blippy


3 Answers

Template deductions do not trigger implicit conversion. Although function (pointer) type is convertible to std::function, this conversion is never part of template substitution.

You will have to construct std::function and pass it to the simlut:

int r = simult(std::function<std::string (int)>(fn), args);
like image 187
SergeyA Avatar answered Nov 15 '22 09:11

SergeyA


Using the suggestion from @SergeyA, I was able to come up with working code, now as a full example:

#include <functional>
#include <iostream>
#include <string>
#include <future>
#include <thread>
#include <vector>

#include <unistd.h> // for sleep. Not needed generally

// declare a function that does what you want
std::string fn(int i)
{
        sleep(i); // number of seconds to sleep for
        std::cout << "Exiting after pausing " << i << " seconds\n";
        return std::to_string(i+1);
}



template<typename T, typename U>
std::vector<T> simult(std::function<T(U)> func, std::vector<U> const &args)
{
        std::vector<std::future<T>> fs;
        for(auto& a:args) fs.push_back(std::async(func, a));

        std::vector<T> results;
        for(auto &f:fs) results.push_back(f.get());
        return results;
}



int main()
{
        std::vector<int> args { 6, 5, 7 };
        std::vector<std::string> results = simult(std::function<std::string (int)>(fn), args);
        std::cout << "Results are:\n";
        for(auto&r:results) std::cout << r << "\n"; //results are returned in correct order
        return 0;
}

Compile:

g++ simult.cc -o simult -lpthread

Run:

time -p simult
Exiting after pausing 5 seconds
Exiting after pausing 6 seconds
Exiting after pausing 7 seconds
Results are:
7
6
8
real 7.00
user 0.00
sys 0.00

I'm still a bit puzzled as to why C++ can't deduce the type of the function by itself, but I guess the important thing is that it works. It just seems that the caller is unnecessarily verbose to me.

Edit: In function simult(), replaced instances of std::string with T.

Edit: changed std::vector<U> args to std::vector<U> const &args

like image 21
blippy Avatar answered Nov 15 '22 10:11

blippy


It doesn't work due to the fact that user defined conversions are not considered in template argument deduction.

You could either help the compiler by explicitly providing the template arguments, and call simult as:

int r = simult<std::string, int>(fn, args);

Live Demo

Or you could make it work out with use of decltype as follows:

std::string fn(int i) {
  return std::to_string(i + 1);
}

template<typename T, typename U>
int simult_impl(std::function<T(U)> f, std::vector<U> const &args) {
  return 666;
}

template<typename F, typename U>
int simult(F f, std::vector<U> const &args) {
  return simult_impl<decltype(f(args[0])), U>(f, args);
}

Live Demo

Edit:

If you want to return a std::vector<T> provided that your compiler supports C++14 you can change as follows:

template<typename T, typename U>
std::vector<T> simult_impl(std::function<T(U)> f, std::vector<U> const &args) {
  return std::vector<T>(10, T{"666"});
}

template<typename F, typename U>
auto simult(F f, std::vector<U> const &args) {
  return simult_impl<decltype(f(args[0])), U>(f, args);
}

Live Demo

like image 43
101010 Avatar answered Nov 15 '22 10:11

101010