constexpr version of ::std::function
Asked Answered
S

3

9

I am in search of a ::std::function usable in constexpr. Use case: I have a function which takes a function pointer as an argument, and a second which passes a lambda to the first function. Both are fully executable at compile time, so I want to constexpr them. Eg:

template <class _Type>
class ConstexprFunctionPtr
{
    private:
        using Type = typename ::std::decay<_Type>::type;
        const Type function;

    public:
        constexpr inline
        ConstexprFunctionPtr(const Type f)
        : function(f)
        { }

        template <typename... Types>
        constexpr inline
        auto
        operator() (Types... args)
        const {
            return function(args... );
        }
};

constexpr inline
void
test()
{
    ConstexprFunctionPtr<int(int)> test([](int i) -> int {
        return i + 1;
    });
    int i = test(100);

    ConstexprFunctionPtr<int(int)> test2([=](int i) -> int {
        return i + 1;
    });
    i = test2(1000);
}

However, this only works because I am converting the lambda to a function pointer, and of course fails with capturing lambdas as showed in the second example. Can anyone give me some pointers on how to do that with capturing lambdas?

This would demonstrate the usecase:

constexpr
void
walkOverObjects(ObjectList d, ConstexprFunctionPtr<void(Object)> fun) {
// for i in d, execute fun
}

constexpr
void
searchObjectX(ObjectList d) {
walkOverObjects(d, /*lambda that searches X*/);
}

Thanks, jack

Update: Thanks for pointing out the C++20 solution, however, I want one that works under C++14

Soggy answered 30/12, 2018 at 12:56 Comment(3)
How do you intent to use it? Why not just use auto test = [](int i) -> int { return i + 1; };?Raft
There's a way to convert a capturing lambda to a function pointer.Resurrect
@Resurrect I do know this way, however, you cant use it in constexpr, as far as I knowSoggy
O
6

I am in search of a ::std::function usable in constexpr

Stop right here. it's impossible. std::function is a polymorphic wrapper function. stateless lambdas, statefull lambdas, functors, function pointers, function references - all of them can build a valid std::function that can change during runtime. so making a compile time equivalent is just a waste of time.

If you just want a compile time generic function parameter, you can just use templates

template<class functor_type>
class my_generic_function_consumer_class{

   using decayed_function_type = typename std::decay_t<functor_type>;

   decayed_function_type m_functor;

};

In your code in question, just accept a generic functor, and validate it using static_assert:

template<class function_type>
constexpr void walkOverObjects(ObjectList d, function_type&& fun) {
    static_assert(std::is_constructible_v<std::function<void(ObjectList), function_type>>,
                  "function_type given to walkOverObjects is invalid.");
}
Openhearth answered 30/12, 2018 at 13:4 Comment(3)
"making a compile time equivalent is just a waste of time.". I don't get your point, std::variant "might" have same arguments in constexpr way. Type erasure of std::function and other functionality might use features not yet compatible with constexpr (memory allocation, cast, ...) but workaround/tradeof might be possible.Poeticize
Hmm... I'm iffy about this. There's no point modifying std::function itself to be constexpr, as it would likely run the risk of being a breaking change. There is, however, a good reason to provide a constexpr equivalent for cases where a generic Callable wrapper is both desired and guaranteed to refer to the same object (which itself is known at compile time) for its entire lifetime.Piscatory
you can't make a constexpr version of std::function. period. std::function does type-erasure which is not possible with constexpr.Openhearth
U
16

So a lot has changed with C++20 --- most importantly, you can now use dynamic memory and virtual functions within constexpr contexts. This makes it entirely possible to build a constexpr version of std::function. Here's a proof-of-concept (it's long and has no copy or move constructors, so please do not use this as-is). It compiles under clang 10, running code here. I've not tried it under other compilers, and it's worth noting that none of the major compilers claim to have a complete implementation of C++-20 at this time.

#include <type_traits>
#include <utility>
#include <functional>

template<typename Ret, typename... Args> struct _function{
    constexpr virtual Ret operator()(Args...) const = 0;
    constexpr virtual ~_function() = default;
};

template<typename F, typename Ret, typename... Args> struct _function_impl : public _function<Ret,Args...>{
    F f;
    constexpr Ret operator()(Args... args) const override {
        return f(std::forward<Args>(args)...);
    }
    constexpr _function_impl(F&& f):f(f){}
};

template<typename > struct function;

template<typename Ret, typename... Args> struct function<Ret (Args...)>{
    _function<Ret,Args...> *real_f{nullptr};
    constexpr Ret operator()(Args... args) const {
        return real_f->operator()(std::forward<Args>(args)...);
    }

    constexpr ~function(){
        if (real_f) delete real_f;
    }

    template<typename F>
    constexpr function(F&& f):real_f(new _function_impl<std::decay_t<F>,Ret,Args...>(std::move(f))){}

};

template<typename Ret, typename... Args>
constexpr Ret call_f_2(const function<Ret(Args...)> &f, Args... a){
    return f(std::forward<Args>(a)...);
}

template<typename F, typename... Args>
constexpr decltype(auto) call_f(F && f, Args&&... a){
    using Ret = std::invoke_result_t<std::decay_t<F>,Args...>;
    function<Ret(Args...)> f2 = std::move(f);
    return call_f_2<Ret,Args...>(f2,a...);
}

int main(){
    constexpr int c = 3;
    constexpr int i = call_f([c](int j) constexpr {return c + j;},4);
    return i;
}
Utas answered 13/4, 2020 at 16:3 Comment(2)
I appreciate you answer, but I wanted a solution for C++11/14, hence I did not mark it as the "correct" one. But I am sure that your effort will be appreciated by people searching for a solution in modern C++ versions.Soggy
Ok! You might want to clarify your question to point this out -- right now it doesn't mention a standard and is tagged with "C++-17"Utas
O
6

I am in search of a ::std::function usable in constexpr

Stop right here. it's impossible. std::function is a polymorphic wrapper function. stateless lambdas, statefull lambdas, functors, function pointers, function references - all of them can build a valid std::function that can change during runtime. so making a compile time equivalent is just a waste of time.

If you just want a compile time generic function parameter, you can just use templates

template<class functor_type>
class my_generic_function_consumer_class{

   using decayed_function_type = typename std::decay_t<functor_type>;

   decayed_function_type m_functor;

};

In your code in question, just accept a generic functor, and validate it using static_assert:

template<class function_type>
constexpr void walkOverObjects(ObjectList d, function_type&& fun) {
    static_assert(std::is_constructible_v<std::function<void(ObjectList), function_type>>,
                  "function_type given to walkOverObjects is invalid.");
}
Openhearth answered 30/12, 2018 at 13:4 Comment(3)
"making a compile time equivalent is just a waste of time.". I don't get your point, std::variant "might" have same arguments in constexpr way. Type erasure of std::function and other functionality might use features not yet compatible with constexpr (memory allocation, cast, ...) but workaround/tradeof might be possible.Poeticize
Hmm... I'm iffy about this. There's no point modifying std::function itself to be constexpr, as it would likely run the risk of being a breaking change. There is, however, a good reason to provide a constexpr equivalent for cases where a generic Callable wrapper is both desired and guaranteed to refer to the same object (which itself is known at compile time) for its entire lifetime.Piscatory
you can't make a constexpr version of std::function. period. std::function does type-erasure which is not possible with constexpr.Openhearth
C
2

A lot has happened since 2020. In C++23 we can now have constexpr std::function. See below from C++ Tip Of The Week:

#include <memory>

template <class>
class function;

template <class R, class... Args>
struct function<R(Args...)> {
  template <class F>
  constexpr function(F f) : ptr{std::make_unique<implementation<F>>(f)} {}

  constexpr auto operator()(Args... args) const -> R {
    return ptr->get(args...);
  }

 private:
  struct interface {
    constexpr virtual auto get(Args...) -> R = 0;
    constexpr virtual ~interface() = default;
  };

  template <class F>
  struct implementation final : interface {
    constexpr explicit(true) implementation(F f) : f{f} {}
    constexpr auto get(Args... args) -> R { return f(args...); }

   private:
    F f;
  };

  std::unique_ptr<interface> ptr;
};

// https://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides

template <class>
struct function_traits {};

template <class R, class G, class... A>
struct function_traits<R (G::*)(A...) const> {
  using function_type = R(A...);
};

template <class F>
using function_type_t = typename function_traits<F>::function_type;

// This overload participates in overload resolution only if &F::operator() is
// well-formed when treated as an unevaluated operand and
// decltype(&F::operator()) is of the form R(G::*)(A...) (optionally
// cv-qualified, optionally noexcept, optionally lvalue reference qualified).
// The deduced type is std::function<R(A...)>.
template <class F>
function(F) -> function<function_type_t<decltype(&F::operator())>>;


consteval auto test_capture() {
  int i = 42;
  function f = [&] { return i; };
  return f();
}

static_assert(42 == test_capture());
Casarez answered 27/7, 2023 at 3:5 Comment(2)
Note that for gcc 13.2 at least, you must specify the dtor of implementation manually. See it live here (version with provided dtor) and there (version with no dtor provided). But upvoted and bookmarked anyway :D !Euripides
But this is probably a bug in this version of gcc, since this isn't required for gcc trunk, nor for clang 17.0.1.Euripides

© 2022 - 2024 — McMap. All rights reserved.