1

I want to call standard library algorithms with the ExecutionPolicy for vectorization. At the same time the call should also work in a constexpr context. Unfortunately the ExecutionPolicy overloads of the algorithms from the standard library are currently not constexpr. At compile time, of course, it doesn't matter if vectorization is used. So in this case I just want to call the overload without ExecutionPolicy.

This I have implemented as:

#include <execution>
#include <functional>
#include <type_traits>

template <typename Fn, typename ... T>
constexpr decltype(auto) unseq_invoke(Fn&& fn, T&& ... v) {
    if (std::is_constant_evaluated()) {
        return std::invoke(
            std::forward<Fn>(fn), std::forward<T>(v) ...);
    } else {
        return std::invoke(
            std::forward<Fn>(fn), std::execution::unseq, std::forward<T>(v) ...);
    }
}

Basically, this also works exactly as it should:

#include <iostream>
#include <string_view>

constexpr bool is_valid_c_str(std::string_view const view) {
    constexpr auto find_wrapper =
        []<typename ... T>(T&& ... v) {
            return std::find(std::forward<T>(v) ...);
        };

    return unseq_invoke(find_wrapper, view.begin(), view.end(), 0) == view.end();
}

int main() {
    using namespace std::literals;
    static constexpr auto a = "valid c_str"sv;
    static constexpr auto b = "invalid\0c_str"sv;

    std::cout << std::boolalpha
        << a << ": " << is_valid_c_str(a) << '\n'
        << b << ": " << is_valid_c_str(b) << '\n';

    static_assert(is_valid_c_str(a));
    static_assert(!is_valid_c_str(b));
}
valid c_str: true
invalidc_str: false

The problem is that I always have to wrap functions like std::find in a lambda. If I try to pass it directly as an argument, I get a <unresolved overloaded function type> error.

return unseq_invoke(std::find, view.begin(), view.end(), 0) == view.end();
main.cpp: In function ‘constexpr bool is_valid_c_str(std::string_view)’:
main.cpp:21:24: error: no matching function for call to ‘unseq_invoke(<unresolved overloaded function type>, std::basic_string_view<char>::const_iterator, std::basic_string_view<char>::const_iterator, int)’
   21 |     return unseq_invoke(std::find, view.begin(), view.end(), 0) == view.end();
      |            ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:6:26: note: candidate: ‘template<class Fn, class ... T> constexpr decltype(auto) unseq_invoke(Fn&&, T&& ...)’
    6 | constexpr decltype(auto) unseq_invoke(Fn&& fn, T&& ... v) {
      |                          ^~~~~~~~~~~~
main.cpp:6:26: note:   template argument deduction/substitution failed:
main.cpp:21:24: note:   couldn’t deduce template parameter ‘Fn’
   21 |     return unseq_invoke(std::find, view.begin(), view.end(), 0) == view.end();
      |            ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Of course, I could shorten that with a macro, but it would still be an explicit wrapper, and I don't like to use macros either.

Is there a way to make unseq_invoke(std::algorithm, ...) work without a wrapper around std::algorithm? Of course, an alternative approach to mine would also help me.

5
  • I think using macro is the most appropriate way in such a case. Commented Mar 26, 2023 at 3:17
  • If you could do it without a macro I think BOOST_HOF_LIFT would use that Commented Mar 31, 2023 at 12:37
  • If you remove the explicit template parameters from lambda, you'll at least get a simpler wrapping: unseq_invoke([](auto&& ...x){return std::find(std::forward<decltype(x)>(x)...);}, ...); but that's still a lot. The macro that @Caleth points, essentially expands to similar code. Commented Apr 2, 2023 at 15:37
  • Yes, probably with my current approach a macro on the call is really the best solution. In the meantime I also came across the addressing restriction :-/ I hope that maybe someone has a basically alternative idea to my approach. Commented Apr 2, 2023 at 16:04
  • 1
    Unrelated to question: Calling forward unqualified is not a good idea. It will do ADL and is likely to prefer an overload found that way, which is definitively not what you want here. You always want to call std::forward. Same for invoke. Commented Apr 7, 2023 at 1:55

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.