Reputation: 60341
Consider the following code and the apply
function :
// Include
#include <iostream>
#include <array>
#include <type_traits>
#include <sstream>
#include <string>
#include <cmath>
#include <algorithm>
// Just a small class to illustrate my question
template <typename Type, unsigned int Size>
class Array
{
// Class body (forget that, this is just for the example)
public:
template <class... Args> Array(const Args&... args) : _data({{args...}}) {;}
inline Type& operator[](unsigned int i) {return _data[i];}
inline const Type& operator[](unsigned int i) const {return _data[i];}
inline std::string str()
{
std::ostringstream oss;
for (unsigned int i = 0; i < Size; ++i)
oss<<((*this)[i])<<" ";
return oss.str();
}
protected:
std::array<Type, Size> _data;
// Apply declaration
public:
template <typename Return,
typename SameType,
class... Args,
class = typename std::enable_if<std::is_same<typename std::decay<SameType>::type, Type>::value>::type>
inline Array<Return, Size> apply(Return (*f)(SameType&&, Args&&...), const Array<Args, Size>&... args) const;
};
// Apply definition
template <typename Type, unsigned int Size>
template <typename Return, typename SameType, class... Args, class>
inline Array<Return, Size> Array<Type, Size>::
apply(Return (*f)(SameType&&, Args&&...), const Array<Args, Size>&... args) const
{
Array<Return, Size> result;
for (unsigned int i = 0; i < Size; ++i) {
result[i] = f((*this)[i], args[i]...);
}
return result;
}
// Example
int main(int argc, char *argv[])
{
Array<int, 3> x(1, 2, 3);
std::cout<<x.str()<<std::endl;
std::cout<<x.apply(std::sin).str()<<std::endl;
return 0;
}
The compilation fails with :
universalref.cpp: In function ‘int main(int, char**)’:
universalref.cpp:45:32: erreur: no matching function for call to ‘Array<int, 3u>::apply(<unresolved overloaded function type>)’
universalref.cpp:45:32: note: candidate is:
universalref.cpp:24:200: note: template<class Return, class SameType, class ... Args, class> Array<Return, Size> Array::apply(Return (*)(SameType&&, Args&& ...), const Array<Args, Size>& ...) const [with Return = Return; SameType = SameType; Args = {Args ...}; <template-parameter-2-4> = <template-parameter-1-4>; Type = int; unsigned int Size = 3u]
universalref.cpp:24:200: note: template argument deduction/substitution failed:
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘long double’
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘float’
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘double’
universalref.cpp:45:32: note: couldn't deduce template parameter ‘Return’
I am not very sure why it fails. In that code, I would like :
apply(std::sin)
So what do I have to change to make it compile ?
Upvotes: 2
Views: 1388
Reputation: 126412
Several overloads exist of function std::sin
, and your function template has no way of deducing one that would produce an exact match (in most situations, no conversions are not considered when deducing template arguments).
First of all, none of those overlads accepts a reference to their first (and only) argument. The compiler is telling you this, so Return
and SameType
cannot be deduced from the type of argument f
:
universalref.cpp:24:200: note: template argument deduction/substitution failed:
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘long double’
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘float’
universalref.cpp:45:32: note: mismatched types ‘SameType&&’ and ‘double’
Therefore, the first step consists in changing the signature of your apply()
function template:
[...] apply(Return (*f)(SameType, Args&&...), [...]
^^^^^^^^
No URef!
Moreover, SFINAE is eliminating all instantiations of your function template where Return
is not deduced to be int
(you are invoking apply()
on an instance of Array<int, 3>
): unfortunately, none of the existing overloads of std::sin
has int
as a return type.
You could try changing your instantiation into Array<double, 3>
, but that alone won't help unfortunately, because of what Paragraph 14.8.2/5 of the C++11 Standard states:
The non-deduced contexts are:
— The nested-name-specifier of a type that was specified using a qualified-id.
— A non-type template argument or an array bound in which a subexpression references a template parameter.
— A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
— A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:
— more than one function matches the function parameter type (resulting in an ambiguous deduction), or
— no function matches the function parameter type, or
— the set of functions supplied as an argument contains one or more function templates.
Because of the overloads requiring integral types to be supported (this is new in C+11, see 26.8/11), your library implementation most likely does define a template overload for std::sin
. This is how stdlibc++'s definition looks like:
template<typename _Tp>
inline _GLIBCXX_CONSTEXPR
typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
double>::__type
sin(_Tp __x)
{ return __builtin_sin(__x); }
There are ways to get out of here, but you probably would not like them. Apart from the above necessary (yet insufficient) changes, you would also need to explicitly cast std::sin
to the desired type. I will also drop the SFINAE condition here, because it does not do anything else than enforcing SameType
to be equal to Type
:
// Declaration in the `Array` class
template <typename Return, class... Args>
inline Array<Return, Size> apply(
Return (*f)(Type, Args&&...),
const Array<Args, Size>&... args
) const;
[...]
// Example
int main(int argc, char *argv[])
{
Array<double, 3> x(1, 2, 3);
// ^^^^^^
// NOTICE
// THIS
std::cout<<x.str()<<std::endl;
std::cout<<x.apply((double(*)(double))std::sin).str()<<std::endl; // OK
// ^^^^^^^^^^^^^^^^^^^
// NOTICE THIS
return 0;
}
Upvotes: 1