Why Can't Operator () of Stateless Functor Be Static

Why can't operator () of stateless functor be static?

Per standard 13.5/6,

An operator function shall either be a non-static member function or be a non-member function and have
at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an
enumeration.

Additionally, in 13.5.4 it is stated that

operator()
shall be a non-static member function with an arbitrary number of parameters. It can have
default arguments. It implements the function call syntax
postfix-expression
(
expression-list
opt
)
where the
postfix-expression
evaluates to a class object and the possibly empty
expression-list
matches
the parameter list of an
operator()
member function of the class. Thus, a call
x(arg1,...)
is interpreted
as
x.operator()(arg1, ...)
for a class object
x
of type
T

Why can overloaded operators not be defined as static members of a class?

I have no specific knowledge of any C++ discussion of this concept, so feel free to ignore this.

But to me, you've got the question backwards. The question should be, "why would this syntax be allowed?"

It provides no advantages at all over the current syntax. The non-static member function version has the same access to private members as your proposed static version. So if you need to access the privates to implement it, just make it a non-static member, exactly as you generally do with most members of a class.

It doesn't make it easier to implement asymmetric operators (ie: operator+(const X &x, const Y &y)). If you need private access to implement this, you'd still need a friend declaration for them in one of the classes.

So I would say that the reason it doesn't exist is that it isn't necessary. Between non-member functions and non-static members, all of the necessary use cases are covered.


Or, to put it another way:

Free functions can do everything that the static function system can, and more.

Through the use of free functions, you can get argument-dependent lookup happening for operators used in templates. You can't do that with static functions, because those would have to be a member of a particular class. And you cannot add to a class from outside of the class, while you can add to a namespace. So if you need to put an operator in a particular namespace in order to make some ADL code work, you can. You can't do that with static function operators.

Thus, free functions are a superset of everything that your proposed static function system would provide. Since there is no benefit to allowing it, there is no reason to allow it, and therefore it is not allowed.


which would make possible to use functors without instantiating them?

That is a contradiction in terms. A "functor" is a "function object". A type is not an object; therefore, it cannot be a functor. It can be a type that, when instantiated, will result in a functor. But the type alone will not be a functor.

Furthermore, being able to declare Typename::operator() static would not mean that Typename() would do what you want. That syntax already has an actual meaning: instantiate a Typename temporary by calling the default constructor.

Lastly, even if all that weren't the case, what good would that actually be? Most template functions that take a callable of some type work just as well with a function pointer as with a functor. Why would you want to restrict your interface, not merely to just functors, but to functors which cannot have internal data? That means you wouldn't be able to pass capturing lambdas and so forth.

What good is a functor that cannot possibly contain state? Why do you want to force the user into passing "functors" that don't have state? And why do you want to prevent the user from being able to use lambdas?

So your question is derived from a false assumption: even if we had it, it wouldn't give you what you want.

When are stateless class functors useful in place of a c style function?

The typical reason is that when you do this:

bool less_than(const Point&, const Point&);
// ...
std::sort(..., &less_than);

The template argument for the predicate is the following:

bool(const Point&,const Point&)

Since the sort function receives a function pointer, it is more difficult for the compiler to inline the predicate use inside std::sort(). This happens because you could have another function

bool greater_than(const Point&, const Point&);

which has the exact same type, meaning the std::sort() instatiation would be shared between the two predicates. (remember that I said that it makes inlining more difficult, not impossible).

In contrast, when you do this:

struct less_than {
bool operator()(const Point&, const Point&) const;
};
// ...
std::sort(..., less_than());

struct greater_than {
bool operator()(const Point&, const Point&) const;
};
// ...
std::sort(..., greater_than());

The compiler generates a unique template instantiation for std::sort() for each predicate, making it easier to inline the predicate's definition.

Stateless lambdas as static local variable

In any case, accessing a nullptr is never a good idea as it is UB.

But we can see that typical implementations generate code which simply works. I try to explain why:

First, it has nothing to do with lambdas. It is simply the not needed using of a copy constructor on a class which has no data. As you have no data, the generated code will not access the passed object. In your case, you "copy" the object which the pointer TFuncOp *pFunc = 0 points to, which is a nullptr which will crash if the object must be accessed. As there is no data to access, a typical implementation will not genrate any code which will access the nullptr at all. But it is still UB.

The same works with all other types in the same way and has nothing special with a lambda!

struct Empty
{
void Do() { std::cout << "This works the same way" << std::endl; }
// int i; // << if you add some data, you get a seg fault
};

int main()
{
Empty* ptr = nullptr;
Empty empty = *ptr; // get seg fault here, because default copy constructor access the nullptr, but typically only if copy ctor needs to access!

empty.Do();
}

And a lambda which has no captured data, is an empty structure with a operator()().

That all is a answer why it seems to work.

What exactly it means that functor in c++ have a state and other regular function doesnt have a state?

"State" refers to data that is remembered and carried between subsequent calls to a function or class method.

Your MyFuncPtr is stateful, as it carries a data member a whose value is set in MyFuncPtr's constructor and is remembered and used through all calls to MyFuncPtr::operator(). You are setting the state once, and then using it over and over. You could just as easily update the state instead, by have MyFuncPtr::operator() save the new value into a and then expose another class method to retrieve the current value of a when needed.

In your example, sum() is also stateful, as it is not a free function, it is actually a non-static member of MyFuncPtr, and thus has access to the same state data (MyFuncPtr::a) that MyFuncPtr::operator() has access to. A better example of a stateless sum() would look more like this instead:

class MyFuncPtr
{
public:
int a;

int operator ()(int b)
{
return a + b;
}

MyFuncPtr(int val)
{
a = val;
}
};

int sum(int a, int b)
{
return a + b;
}

int main()
{
cout << "Hello World!\n";

MyFuncPtr obj(5);
cout << obj(15) << endl;

cout << sum(5, 15) << endl;
}

Testing static replacement for std::function for non stateless lambda support

ETL documents that etl::delegate doesn't own the lambda at all, see https://www.etlcpp.com/delegate.html. It only stores a pointer to the passed object. It doesn't store the lambda at all. See also the code at https://github.com/ETLCPP/etl/blob/master/include/etl/private/delegate_cpp11.h#L117.

Contrary to what the linked documentation seems to imply, it is always undefined behavior to pass the constructor of etl::delegate a lambda expression directly as argument, no matter whether it is stateless or not. Because etl::delegate stores a pointer to the passed object, when operator() is called it will try to call a non-static member function on an out-of-lifetime object.

The undefined behavior is just less likely to cause unintended behavior of the compiled program if the lambda is stateless and so the compiled member function called on the invalid pointer doesn't actually have to access/dereference the pointer.

You must store the lambda somewhere outside the etl::delegate.

I am not sure why the author didn't add a lambda-to-function-pointer conversion for non-capturing lambdas and/or disallowed rvalue arguments to the constructor. The way it is written now it is extremely easy to misuse.


vl::Func seems to implement a pattern similar to std::function and does own the passed callable. That also means it uses new to store the callable in dynamic memory, but no matching delete as far as I can tell.

Therefore it is not a static-allocation replacement. It does use dynamic allocation.

(Sorry for my previous assertion that it is leaking the object. I completely misread the code. I don't see any defect in the implementation anymore as far as the superficial look I had at the code.)


It is not possible to implement what std::function does in generality without dynamic allocation. A static allocation replacement for std::function which also owns the callable must have some parameter limiting the maximum size of a callable object it can store or something similar because the callable would need to be embedded directly into the storage of the std::function-replacement object.

From the examples above you can see that problem. Both of them don't have such a parameter. So one of the replacements isn't owning and the other isn't statically allocating, they can't be doing both.


To test whether the lambda is stateless you need to apply is_stateless to the type of the lambda expression, e.g.

auto lambda = [&args...](Func v, int& ret) { ret = v(args...); };

CHECK_EQUAL(true, is_stateless<decltype(lambda)>::value);

You are applying is_stateless to a type that is not a lambda at all.

Can functor provided to std::generate be stateful?

Introduction

There's no problem using a stateful functor with functions such as std::generate, but one has to be careful not to run into issues where the underlying implementation makes a copy that will change the semantics of a program in a way that is different from what the developer have in mind.

25.1p10 Algorithms library - General [algorithms.general]

[ Note: Unless otherwise specified, algorithms that take function objects as arguments are permitted to copy those function obejcts freely. Programmers for whom object identity is imoprtant should consider using a wrapper class that points to a noncopied implementation object such as reference_wrapper, or some equivalent solution. -- end note ]



Order of assignment/evaluation

The standard explicitly states that exactly last - first (N) assignments and invocations of the generator will be made, but it doesn't state in what order. More can be read in the following Q&A:

  • C++ standard wording: Does “through all iterators in the range” imply sequentiality?


Stateful functors + std::generate, unsafe?

Generally no, but there are a few caveats.

Within std::generate the standard guarantees that the same instance of the functor type will be invoked for every element in the given range, but as can be hinted by the declaration of std::generate we might run into issues where we forget that the passed functor invoked inside the function will be a copy of the one passed as argument.

See the below snippet where we declare a functor to generate "unique" ids:

#include <iostream>
#include <algorithm>

template<class id_type>
struct id_generator {
id_type operator() () {
return ++idx;
}

id_type next_id () const {
return idx + 1;
}

id_type idx {};
};

int main () {
id_generator<int> gen;

std::vector<int> vec1 (5);
std::vector<int> vec2 (5);

std::generate (vec1.begin (), vec1.end (), gen);
std::generate (vec2.begin (), vec2.end (), gen);

std::cout << gen.next_id () << std::endl; // will print '1'
}

After running the above we might expect gen.next_id () to yield 11, since we have used it to generate 5 ids for vec1, and 5 ids for vec2.

This is not the case since upon invoking std::generate our instance of id_generator will be copied, and it is the copy that will be used inside the function.



What would be the solution?

There are several solutions to this problem, all of which prevents a copy from being made when you pass your functor to some algorithm function related to std::generated.


Alternative #1

The recommended solution is to wrap your functor in a std::reference_wrapper with the use of std::ref from <functional>. This will effectively copy the reference_wrapper, but the referred to instance of generate_id will stay the same.

std::generate (vec1.begin (), vec1.end (), std::ref (gen));
std::generate (vec2.begin (), vec2.end (), std::ref (gen));

std::cout << gen.next_id () << std::endl; // will print '11'

Alternative #2

You could, of course, also make your fingers stronger by writing something as confusing as the below:

std::generate<decltype(vec1.begin()), id_generator<int>&>(vec1.begin(), vec1.end(), gen);

Can a class member function be invoked without an object?

Printer() is an instance of the Printer class. It will result in a temporary object of type Printer which is passed to std::for_each.

This is the object on which operator() is called by std::for_each internally.

Without an object of type Printer, it is not possible to call the operator() member function.



Related Topics



Leave a reply



Submit