llvm/llvm-project

[libc++] std::invoke() substitution failure when using function object with default argument

tcbrindle opened this issue · 4 comments

The following test program compiles with libc++17 and libstdc++, but fails with libc++18:

#include <concepts>
#include <functional>
#include <ranges>
#include <source_location>

#define FWD(x) static_cast<decltype(x)&&>(x)

struct add_fn {
    template <std::integral T>
    constexpr auto operator()(T lhs, T rhs,
                              std::source_location loc = std::source_location::current())
        const -> T
    {
        return lhs + rhs;
    }
};

inline constexpr auto add = add_fn{};

struct fold_op {
    template <typename Rng, typename Func, typename Init>
    constexpr auto operator()(Rng&& rng, Func func, Init init) const
    {
        auto init_ = std::ranges::range_value_t<Rng>(std::move(init));
        for (auto&& elem : rng) {
            init_ = std::invoke(func, std::move(init_), FWD(elem));
        }
        return init_;
    }
};

inline constexpr auto fold = fold_op{};


auto sum(std::span<int const> arr) -> int
{
    return fold(arr, add, 0);
}

https://godbolt.org/z/h47dnM3eP

The error is:

<source>:27:21: error: no matching function for call to 'invoke'
   27 |             init_ = std::invoke(func, std::move(init_), FWD(elem));
      |                     ^~~~~~~~~~~
<source>:38:16: note: in instantiation of function template specialization 'fold_op::operator()<std::span<const int> &, add_fn, int>' requested here
   38 |     return fold(arr, add, 0);
      |                ^
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__functional/invoke.h:27:1: note: candidate template ignored: substitution failure [with _Fn = add_fn &, _Args = <__libcpp_remove_reference_t<int &>, const int &>]
   27 | invoke(_Fn&& __f, _Args&&... __args) noexcept(is_nothrow_invocable_v<_Fn, _Args...>) {
      | ^
1 error generated.
Compiler returned: 1

Removing the source_location default argument from add_fn::operator() allows the code to compile as expected.

This appears to be a Clang regression in Clang 18, not in libc++.

Reduced: https://godbolt.org/z/TMc3Wqjb8

template <class _Fp, class... _Args>
decltype(_Fp{}(0, 0))
__invoke(_Fp&& __f);

template<typename T>
struct type_identity { using type = T; };

template<class Fn>
struct invoke_result : type_identity<decltype(__invoke(Fn{}))> {};

namespace std {
struct source_location {
  struct __impl {
    const char *_M_file_name;
    const char *_M_function_name;
    unsigned _M_line;
    unsigned _M_column;
  };
};
}

struct add_fn {
    template <typename T>
    constexpr auto operator()(T lhs, T rhs,
                              const std::source_location::__impl* = __builtin_source_location())
        const -> T
    {
        return lhs + rhs;
    }
};

using i = invoke_result<add_fn>::type;
static_assert(__is_same(i, int));
<source>:39:25: error: unknown type name 'i'
   34 | static_assert(__is_same(i, int));
      |                         ^

Started with 8c2b0d4 / #78436 (CC @cor3ntin)

@llvm/issue-subscribers-clang-frontend

Author: Tristan Brindle (tcbrindle)

The following test program compiles with libc++17 and libstdc++, but fails with libc++18:
#include &lt;concepts&gt;
#include &lt;functional&gt;
#include &lt;ranges&gt;
#include &lt;source_location&gt;

#define FWD(x) static_cast&lt;decltype(x)&amp;&amp;&gt;(x)

struct add_fn {
    template &lt;std::integral T&gt;
    constexpr auto operator()(T lhs, T rhs,
                              std::source_location loc = std::source_location::current())
        const -&gt; T
    {
        return lhs + rhs;
    }
};

inline constexpr auto add = add_fn{};

struct fold_op {
    template &lt;typename Rng, typename Func, typename Init&gt;
    constexpr auto operator()(Rng&amp;&amp; rng, Func func, Init init) const
    {
        auto init_ = std::ranges::range_value_t&lt;Rng&gt;(std::move(init));
        for (auto&amp;&amp; elem : rng) {
            init_ = std::invoke(func, std::move(init_), FWD(elem));
        }
        return init_;
    }
};

inline constexpr auto fold = fold_op{};


auto sum(std::span&lt;int const&gt; arr) -&gt; int
{
    return fold(arr, add, 0);
}

https://godbolt.org/z/h47dnM3eP

The error is:

&lt;source&gt;:27:21: error: no matching function for call to 'invoke'
   27 |             init_ = std::invoke(func, std::move(init_), FWD(elem));
      |                     ^~~~~~~~~~~
&lt;source&gt;:38:16: note: in instantiation of function template specialization 'fold_op::operator()&lt;std::span&lt;const int&gt; &amp;, add_fn, int&gt;' requested here
   38 |     return fold(arr, add, 0);
      |                ^
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__functional/invoke.h:27:1: note: candidate template ignored: substitution failure [with _Fn = add_fn &amp;, _Args = &lt;__libcpp_remove_reference_t&lt;int &amp;&gt;, const int &amp;&gt;]
   27 | invoke(_Fn&amp;&amp; __f, _Args&amp;&amp;... __args) noexcept(is_nothrow_invocable_v&lt;_Fn, _Args...&gt;) {
      | ^
1 error generated.
Compiler returned: 1

Removing the source_location default argument from add_fn::operator() allows the code to compile as expected.

DNKpp commented

Will there ever be a patch for clang-18? Or will this be broken for ever for this whole major version?

@DNKpp We're in the LLVM 20 release cycle now, so fixes will only be back-ported to LLVM 19.