3

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<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);
}
Derrick Turk
  • 4,246
  • 1
  • 27
  • 27
  • 1
    Yeah, but the bug seems to be in `libc++`; compiling with `clang++-3.5` and gcc-4.8's `libstdc++` gives me no errors. – Massa Jun 17 '14 at 01:17
  • I would go further and risk that the problem is in the implementation of `std::array` by `libc++`... – Massa Jun 17 '14 at 13:27
  • Related: [Returning a tuple from a function using uniform initialization syntax](http://stackoverflow.com/questions/14961809/returning-a-tuple-from-a-function-using-uniform-initialization-syntax). – Casey Jun 17 '14 at 15:32

1 Answers1

3

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<double> (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<class Fn, class TupleArr>
struct needs_apply {
    template <class F=Fn>
    static auto test(int) ->
      decltype(std::declval<F>()(*std::declval<TupleArr>().begin()), std::false_type{});
    static auto test(...) -> std::true_type;
    using type = decltype(test(0));
};

template<class Fn, class TupleArr>
void tuple_array_map(Fn f, const TupleArr& arr, std::false_type)
{
    for (auto&& i : arr)
        static_cast<void>(f(i));
}

template<class Fn, class TupleArr>
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<class Fn, class TupleArr>
void tuple_array_map(Fn&& f, TupleArr&& arr) {
    tuple_array_map(std::forward<Fn>(f), std::forward<TupleArr>(arr),
                    typename needs_apply<Fn,TupleArr>::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.

Community
  • 1
  • 1
Casey
  • 41,449
  • 7
  • 95
  • 125