Parzival
Parzival

Reputation: 3

No matching function for call to object of type 'const std::__1::packaged_task<void ()>', but `std::is_const` returns false

#include <functional>
#include <future>
#include <iostream>
#include <thread>

template<class Func, class... Args>
void submit(Func&& f, Args&&... args) {
  using returnType = typename std::result_of<Func(Args...)>::type;
  auto task1 = std::packaged_task<returnType()>(std::bind(std::forward<Func>(f), std::forward<Args>(args)...));
  std::cout << std::is_const<decltype(task1)>::value << " task1 const or not" << std::endl;

  auto tmp = [task2=std::move(task1)]() {
    std::cout << std::is_const<decltype(task2)>::value << " const or not" << std::endl;  // print 0, which means non-const
    // task2();  // uncomment this line, compilation will fail
  };
  tmp();

}

int main() {
  submit([&] {
    std::cout << "fooooooooo" << std::endl;
  });
  return 0;
}

I know the meaning of error; I know making lambda mutable will help and I test it, but I want to know where does the const come from. Note that std::is_const returns false, which makes me really confused.

EDIT: Sorry forgot to mention the compiler. I was using clang-1000.10.44.2. The command is clang++ -std=c++14 test.cpp -o test

Upvotes: 0

Views: 1823

Answers (1)

I'm going to assume you are using Clang up to version 7.0. This behavior is documented by bug report 38325. However, it also presents rationale why the behavior you see is not totally unreasonable. To quote Richard Smith from the bug report:

Per [expr.prim.id.unqual]p2, the type of 'x' or 'y' within the lambda is the type of a class member access expression naming the corresponding capture. Those members are of type (non-const) int, but the lambda's *this parameter is const-qualified

To explain it, recall that a lambda expression introduces a unique closure type. It's as if you wrote:

struct unique {
    decltype(task1) task2;
    void operator()() const { /* Your code */ }
};
unique const tmp = unique{std::move(tmp1)};

Now while tmp is const, the "type of the entity named" by the identifier task2 is not const qualified (the member type has no cv-qualifications). So there you have it. Even though task2 when used as a postfix-expression to the left of a function call operator preserves const qualifications, you may not see that when inspecting decltype(task2). The workaround is to force task2 to be treated as a regular expression, not subject to the special rules of decltype for id-expressions. You do that by adding parentheses:

std::is_const<std::remove_reference_t<decltype((task2))>>::value

decltype((task2)) will be appropriate to the type and value category of (task2), which is decltype(task1) const&. remove_reference_t gives us decltype(task1) const and the predicate you check reports what you would expect.

Upvotes: 1

Related Questions