I'm working on a project which involves providing an interface for users to find optima of functions of arbitrary numbers of arguments. Internally, all the mechanism is built around std::tuple
s of the argument types. I want to provide users the ability to call my optimization routines, though, on functions written in the "usual" style (such as f1
in the example), rather than having to write their functions to be optimized as functions of std::tuple
instantiations (such as f2
in the example).
As part of this mechanism, I have written an apply
function which unpacks a tuple into the arguments of a given function and calls it.
I have also created a pair of function templates, one forwarding to the other with a lambda wrapper, providing the interface to the optimization routine. A simplified version appears below as tuple_array_map
. The intention was to provide SFINAE for selection between the two, depending on whether the function type is callable with a tuple argument, or callable with the unpacked tuple members as arguments. I use dummy template parameters with SFINAE-triggering default arguments for this purpose.
This scheme works perfectly under g++ 4.7 and higher and compiling with -std=c++11 -pedantic -Wall -Wextra -Werror
produces no warnings or errors.
However, when trying to compile under clang 5.1 with -std=c++11
(sorry, I'm not a big clang user and I don't know if there's a more appropriate set of options), I get the following output for my example code:
clang_fail.cpp:91:5: error: call to 'tuple_array_map' is ambiguous
tuple_array_map(f2, tuples);
^~~~~~~~~~~~~~~
clang_fail.cpp:59:6: note: candidate function [with Fn = double (*)(const
std::__1::tuple<double> &), TupleArr =
std::__1::array<std::__1::tuple<double>, 5>, $2 = double]
void tuple_array_map(Fn f, const TupleArr& arr)
^
clang_fail.cpp:69:6: note: candidate function [with Fn = double (*)(const
std::__1::tuple<double> &), TupleArr =
std::__1::array<std::__1::tuple<double>, 5>, $2 = double, $3 = void]
void tuple_array_map(Fn f, const TupleArr& arr)
^
clang_fail.cpp:71:5: error: call to 'tuple_array_map' is ambiguous
tuple_array_map([&](const typename TupleArr::value_type& t) {
^~~~~~~~~~~~~~~
clang_fail.cpp:90:5: note: in instantiation of function template specialization
'tuple_array_map<double (*)(double),
std::__1::array<std::__1::tuple<double>, 5>, double, void>' requested here
tuple_array_map(f1, tuples);
^
clang_fail.cpp:59:6: note: candidate function [with Fn = <lambda at
clang_fail.cpp:71:21>, TupleArr = std::__1::array<std::__1::tuple<double>,
5>, $2 = double]
void tuple_array_map(Fn f, const TupleArr& arr)
^
clang_fail.cpp:69:6: note: candidate function [with Fn = <lambda at
clang_fail.cpp:71:21>, TupleArr = std::__1::array<std::__1::tuple<double>,
5>, $2 = double, $3 = void]
void tuple_array_map(Fn f, const TupleArr& arr)
^
The really puzzling part is that it appears to deduce a double
return from a call expression that should SFINAE out, unless I've missed something from the standard regarding either template default arguments or SFINAE itself.
Example follows---it's as minimal as I could get it while still triggering the same behavior:
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
double f1(double x)
{
return x * 2;
}
double f2(const std::tuple<double>& x)
{
return std::get<0>(x) * 2;
}
template<std::size_t N>
struct apply_impl {
template<class F, class Tuple, class... TParams>
static auto apply(F&& fn, Tuple&& t, TParams&&... args)
-> decltype(
apply_impl<N - 1>::apply(
std::forward<F>(fn), std::forward<Tuple>(t),
std::get<N - 1>(std::forward<Tuple>(t)),
std::forward<TParams>(args)...
))
{
return apply_impl<N - 1>::apply(
std::forward<F>(fn), std::forward<Tuple>(t),
std::get<N - 1>(std::forward<Tuple>(t)),
std::forward<TParams>(args)...
);
}
};
template<>
struct apply_impl<0> {
template<class F, class Tuple, class... TParams>
static auto apply(F&& fn, Tuple&&, TParams&&... args)
-> decltype(std::forward<F>(fn)(std::forward<TParams>(args)...))
{
return std::forward<F>(fn)(std::forward<TParams>(args)...);
}
};
template<class F, class Tuple>
auto apply(F&& fn, Tuple&& t)
-> decltype(apply_impl<
std::tuple_size<typename std::decay<Tuple>::type>::value
>::apply(std::forward<F>(fn), std::forward<Tuple>(t)))
{
return apply_impl<
std::tuple_size<typename std::decay<Tuple>::type>::value
>::apply(std::forward<F>(fn), std::forward<Tuple>(t));
}
template<class Fn, class TupleArr,
class = decltype(std::declval<Fn>()(
std::declval<typename TupleArr::value_type>()))>
void tuple_array_map(Fn f, const TupleArr& arr)
{
for (auto i = 0; i < arr.size(); ++i)
static_cast<void>(f(arr[i]));
}
template<class Fn, class TupleArr,
class = decltype(apply(std::declval<Fn>(),
std::declval<typename TupleArr::value_type>())),
class = void>
void tuple_array_map(Fn f, const TupleArr& arr)
{
tuple_array_map([&](const typename TupleArr::value_type& t) {
return apply(f, t);
}, arr);
}
int main()
{
std::array<std::tuple<double>, 5> tuples = {
std::make_tuple(1),
std::make_tuple(2),
std::make_tuple(3),
std::make_tuple(4),
std::make_tuple(5)
};
// "apply" unpacks a tuple into arguments to a function
apply(f1, tuples[0]);
// this call produces an ambiguity one level down under clang
tuple_array_map(f1, tuples);
// this call directly produces an ambiguity under clang
tuple_array_map(f2, tuples);
}