Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(g++ 4.7.1) Replacing explicit type name with an equivalent class typedef fails to compile

I'm trying to create a templated function which accepts an iterable and a function, such that the passed function will be implicitly casted to a std::function of the appropriate type (thus allowing it to be used with both full functions and lambdas).

Here's the code:

#include <iostream>
#include <vector>
#include <algorithm>
#include <typeinfo>


template<typename T>
void bar(const T & base, std::function<bool(int)> f) // works
//void bar(const T & base, std::function<bool(typename T::iterator::value_type)> f) // fails to compile
{
    std::cout << ((typeid(std::function<bool(int)>) == typeid(std::function<bool(typename T::iterator::value_type)>))?"identical":"distinct") << std::endl;
}

bool filter(int x) { return x%2==0; }

int main() { bar(std::vector<int> {0, 1}, filter); }

Compiled with g++-4.7 -std=c++11 -o itest itest.cpp this produces identical.

If you uncomment line 10 and comment line 9 and compile as above, instead compilation fails with

g++-4.7 -std=c++11 -Wall -Werror  -o itest itest.cpp
itest.cpp: In function 'int main()':
itest.cpp:16:53: error: no matching function for call to 'bar(std::vector<int>, bool (&)(int))'
itest.cpp:16:53: note: candidate is:
itest.cpp:9:10: note: template<class T> void bar(const T&, std::function<bool(typename T::iterator::value_type)>)
itest.cpp:9:10: note:   template argument deduction/substitution failed:
itest.cpp:16:53: note:   mismatched types 'std::function<bool(typename T::iterator::value_type)>' and 'bool (*)(int)'

I should note that the unmodified version succeeds with Xcode (having set the appropriate options), but I'd prefer to stick with g++ over clang if possible. Am I doing something wrong, or is this a known bug in g++?

like image 677
Bakkot Avatar asked Sep 03 '12 17:09

Bakkot


2 Answers

Sorry, but the bug is in your code. It's equivalent to:

template<typename T> struct S { template<typename U> S(const U &); };
template<typename T> void bar(T, S<T>);
int main() { bar(5, 6); }

The issue is that in template argument deduction/substitution, if a template argument appears (either directly or in constructing a dependent type) in more than one argument then both arguments have to match exactly; user-defined conversions are not considered, even if it is obvious from one argument what the type has to be.

The user-defined conversion here is the implicit constructor of std::function<...>.

Possible fix would be to instantiate bar explicitly (as bar<int>), or to dispatch to a helper function:

template<typename T>
void bar_impl(const T & base, std::function<bool(typename T::iterator::value_type)> f)
{
    std::cout << ((typeid(std::function<bool(int)>) == typeid(std::function<bool(typename T::iterator::value_type)>))?"identical":"distinct") << std::endl;
}

template<typename T, typename F>
void bar(const T & base, F &&f)
{
    bar_impl<T>(base, std::forward<F>(f));
}
like image 107
ecatmur Avatar answered Nov 18 '22 06:11

ecatmur


You need second overload for pointer function -- then it will compile. Implicit cast to std::function wont work:

void bar(const T & base, bool(*f)(typename T::value_type)){
    std::cout << "ptr func\n";
}

Work around for problem described by ecatmur (several T, with not matching types in function signutre): you can wrap other T in identity struct, which is defined like so:

template<class T> struct identity{ typedef T type; };

then compiler will ignore these T's for type deduction.

like image 2
Leonid Volnitsky Avatar answered Nov 18 '22 05:11

Leonid Volnitsky