11

Suppose I have a callable type like so:

struct mutable_callable
{
    int my_mutable = 0;
    int operator()() { // Not const
        return my_mutable++;
    }
};

Note that mutable_callable has a non-const operator() that modifies a member variable.....

Now suppose I create a std::function out of my type:

std::function<int()> foo = mutable_callable{};

Now I can do this:

void invoke(std::function<int()> const& z)
{
    z();
}

int main()
{
    invoke(foo); // foo changed.....oops
}

Now as far as I can tell std::functions operator() is const as per: https://en.cppreference.com/w/cpp/utility/functional/function/operator()

So my gut feeling is that you shouldn't be able to do this.....

But then looking at: https://en.cppreference.com/w/cpp/utility/functional/function/function

This doesn't seem to put any constraints on whether or not the callable type has a constant operator()......

So my question is this: I am correct in assuming that std::function<int()> const& is essentially the same thing as std::function<int()>& that is there is no actually difference between the behavior of the two......and if that is the case why is it not const correct?

DarthRubik
  • 3,927
  • 1
  • 18
  • 54
  • @MaxLanghof No.....`std::function` has the equivalent to a `struct a{ std::any x; };` in it..... – DarthRubik Dec 04 '19 at 15:33
  • Here is a small snippet of the internals of the MSVC `std::function` implementation: https://i.stack.imgur.com/eNenN.png where `using _Ptrt = _Func_base<_Ret, _Types...>`. I rest my case. – Max Langhof Dec 04 '19 at 15:36

1 Answers1

4

This boils down to the same as struct A { int* x; };, where in a const A a; you can modify the value of *(a.x) (but not where it points to). There is a level of indirection in std::function (from the type erasure) through which const is not propagated.

And no, std::function<int()> const& f is not pointless. In a std::function<int()>& f you would be able to assign a different functor to f, which you cannot do in the const case.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • Yup.....that actually makes a lot of sense.....still confusing at first glance though – DarthRubik Dec 04 '19 at 15:38
  • I believe it's a design flaw. This indirection should be an implementation detail transparent to the user, and the constness could be propagated using some metaprogramming. – Igor R. Dec 04 '19 at 15:49
  • @IgorR. Yes, the constness could be propagated. `std::vector` does this, `std::unique_ptr` doesn't. I feel `std::function` is not really about expressing invariants of the functor state though. Maybe we could repurpose abominable function types (i.e. `std::function`) to distinguish? – Max Langhof Dec 04 '19 at 16:07
  • `unique_ptr` should not propagate constness, as the regular pointer does not. And `std::function` wouldn't compile. – Igor R. Dec 04 '19 at 16:41
  • 1
    @IgorR. I know. My point was that some parts of the standard library do it and some others don't. Which category `std::function` should fall into is not clear to me. And the `std::function` was a hypothetical - of course it doesn't compile now, but would it satisfy e.g. the OP here if that could be made valid, expressing "can only be assigned functors with a `operator() const` (or stateless ones)"? (even if behind the scenes that would be quite atrocious, due to using abominable function types)? – Max Langhof Dec 04 '19 at 16:58