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::tuples 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 &), TupleArr =
std::__1::array, 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 &), TupleArr =
std::__1::array, 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 std::__1::array, 5>, double, void>' requested here
tuple_array_map(f1, tuples);
^
clang_fail.cpp:59:6: note: candidate function [with Fn = clang_fail.cpp:71:21>, TupleArr = std::__1::array,
5>, $2 = double]
void tuple_array_map(Fn f, const TupleArr& arr)
^
clang_fail.cpp:69:6: note: candidate function [with Fn = clang_fail.cpp:71:21>, TupleArr = std::__1::array,
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
#include
#include
#include
double f1(double x)
{
return x * 2;
}
double f2(const std::tuple& x)
{
return std::get<0>(x) * 2;
}
template
struct apply_impl {
template
static auto apply(F&& fn, Tuple&& t, TParams&&... args)
-> decltype(
apply_impl::apply(
std::forward(fn), std::forward(t),
std::get(std::forward(t)),
std::forward(args)...
))
{
return apply_impl::apply(
std::forward(fn), std::forward(t),
std::get(std::forward(t)),
std::forward(args)...
);
}
};
template<>
struct apply_impl<0> {
template
static auto apply(F&& fn, Tuple&&, TParams&&... args)
-> decltype(std::forward(fn)(std::forward(args)...))
{
return std::forward(fn)(std::forward(args)...);
}
};
template
auto apply(F&& fn, Tuple&& t)
-> decltype(apply_impl<
std::tuple_size::type>::value
>::apply(std::forward(fn), std::forward(t)))
{
return apply_impl<
std::tuple_size::type>::value
>::apply(std::forward(fn), std::forward(t));
}
template class = decltype(std::declval()(
std::declval()))>
void tuple_array_map(Fn f, const TupleArr& arr)
{
for (auto i = 0; i < arr.size(); ++i)
static_cast(f(arr[i]));
}
template class = decltype(apply(std::declval(),
std::declval())),
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, 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);
}
Answer
The ambiguity when compiling with libc++ is due to the lack of the standard-mandated explicit specifier on std::tuple's converting constructor (Constructor #2 at cppreference). Consequently, double is implicitly convertible to std::tuple (See this example program) so both of your tuple_apply_map functions are viable.
As a workaround, I suggest creating a needs_apply trait and using that to constrain your tuple_apply_map templates (I'll use tag dispatching):
template
struct needs_apply {
template
static auto test(int) ->
decltype(std::declval()(*std::declval().begin()), std::false_type{});
static auto test(...) -> std::true_type;
using type = decltype(test(0));
};
template
void tuple_array_map(Fn f, const TupleArr& arr, std::false_type)
{
for (auto&& i : arr)
static_cast(f(i));
}
template
void tuple_array_map(Fn f, const TupleArr& arr, std::true_type)
{
tuple_array_map([&](const typename TupleArr::value_type& t) {
return apply(f, t);
}, arr, std::false_type{});
}
template
void tuple_array_map(Fn&& f, TupleArr&& arr) {
tuple_array_map(std::forward(f), std::forward(arr),
typename needs_apply::type{});
}
This works correctly with libc++ and with libstdc++ and even compiling with g++.
According to this answer by Howard Hinnant, this non-conformance of the std::tuple constructor is an extension implemented in libc++ as an experiment.
See also Library Working Group active issue 2051 and the paper N3680 written by Daniel Krügler to address the issue.
No comments:
Post a Comment