Template function with std::async in clang
Asked Answered
G

1

6

I was looking at the example for std::async here, as follows:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>

template <typename RAIter>
int parallel_sum(RAIter beg, RAIter end)
{
    auto len = std::distance(beg, end);
    if(len < 1000)
        return std::accumulate(beg, end, 0);

    RAIter mid = beg + len/2;
    auto handle = std::async(std::launch::async,
                              parallel_sum<RAIter>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}

int main()
{
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
}

I tried compiling it with the web compiler for Clang 3.4 and it resulted in the output The sum is instead of the expected The sum is 1000.

I copied the example and compiled with clang 3.5-1ubuntu1 / gcc 4.8 on Ubuntu 14.04.1 64-bit using the following command:

clang++ -g main.cpp -std=c++1y -o out -pthread;

I get the following error:

main.cpp:15:19: error: no matching function for call to 'async'

    auto handle = std::async(std::launch::async,
                  ^~~~~~~~~~
main.cpp:24:35: note: in instantiation of function template specialization
      'parallel_sum<__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> >
      > >' requested here
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
                                  ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/future:1523:5: note: 
      candidate template ignored: substitution failure [with _Fn = int
      (__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >), _Args =
      <__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &>]:
      function cannot return function type 'int (__gnu_cxx::__normal_iterator<int *,
      std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int *,
      std::vector<int, std::allocator<int> > >)'
    async(launch __policy, _Fn&& __fn, _Args&&... __args)
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/future:1543:5: note: 
      candidate template ignored: substitution failure [with _Fn = std::launch, _Args = <int
      (__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >),
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &>]: no
      type named 'type' in 'std::result_of<std::launch (int
      (*)(__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >),
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &,
      __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > &)>'
    async(_Fn&& __fn, _Args&&... __args)
    ^
1 error generated.
make: *** [all] Error 1

Is this a bug in clang, gcc, libstdc++, or am I missing something?

Gerome answered 7/8, 2014 at 14:50 Comment(4)
Hm. The culprit is here: with _Fn = int (__gnu_cxx::__normal_iterator<..>, __gnu_cxx::__normal_iterator<..>) This should be an lvalue-reference. Seems there's a problem with deduction.Protuberance
OK, I've boiled it down to the following issue: template<class T> void deduce(T&&) { /* print type T */ }. for deduce(parallel_sum<int*>);, clang++ deduces T == int(int*,int*) whereas g++ deduces T == int(&)(int*,int*). This causes the problem.Protuberance
This looks very much like a clang++ bug. If the function is not a function template specialization, it deduces an lvalue reference. Live exampleProtuberance
As a workaround, clang does compile if the argument to async is static_cast<decltype((parallel_sum<RAIter>))>(parallel_sum) :OHaustorium
P
3

I think this is a bug in clang++. Unless there's a strange restricted rule that I'm not aware of, an id-expression referring to a function is an lvalue. However, clang++ makes a distinction between function template specializations and ordinary functions in the deduction for a universal reference:

#include <iostream>

template<class T>
void print_type()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

template <class T>
int foo(bool) { return 42; }

int bar(bool) { return 42; }

template<class T>
void deduce(T&&)
{
    print_type<T>();
}

int main()
{
    deduce(foo<bool>);
    deduce(bar);
}

Output, clang++ up to and including an early 3.5:

void print_type() [T = int (bool)]
void print_type() [T = int (&)(bool)]

Live example


std::result_of is used in libstdc++'s implementation of std::async to get the return type of the function (snippet from here):

template<typename _Fn, typename... _Args>
future<typename result_of<_Fn(_Args...)>::type>
async(launch __policy, _Fn&& __fn, _Args&&... __args)

If we pass foo<bool> as the second argument, clang++ deduces _Fn == int (bool).

The type of the function (object) is combined with the argument types for result_of. This is probably a relict of C++03, where we didn't have variadic templates yet. The argument types are passed to allow result_of to resolve overloaded functions like an overloaded operator() in case _Fn is a class type.

However, if _Fn is deduced not to a function reference, but to a function type, the combination _Fn(_Args...) forms an illegal type: a function returning a function:

     _Fn           == int(bool)
     _Args...      == bool
==>  _Fn(_Args...) == int(bool)(bool)

But there's more to it: The above declaration of async is defective, see LWG 2021. Howard Hinnant changed the declaration in libc++ to:

template <class F, class... Args>
future < typename result_of<
             typename decay<F>::type(typename decay<Args>::type...)
         >::type
       >
async(launch policy, F&& f, Args&&... args);

so libc++ decays the function to a function pointer. The problem caused by the missing lvalue reference disappears.

Protuberance answered 7/8, 2014 at 16:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.