2

I am wondering if this is fine to do, it compiles fine for me but I am not sure if it actually is ok:

class Foo {
  std::function<Foo(Foo& f)> my_function;
};

The function my_function takes a Foo reference and returns a Foo. It is a member variable inside Foo, and usually these incomplete types are not allowed. For example:

class Foo {
   std::vector<Foo> foos;
};

Would not work I don't think (a benefit of boost is that it does work for boost::container::vector).

Barry
  • 286,269
  • 29
  • 621
  • 977
chewinggum
  • 229
  • 1
  • 3
  • 7
  • 3
    Actually `vector` is fine from c++17. – cigien May 31 '20 at 20:00
  • 6
    Std function is not a lambda, and a lambda is not std function. This is akin to asking about ints, when your code contains nothing but doubles. You can convert lambdas to std functions, but they are not the same thing. – Yakk - Adam Nevraumont May 31 '20 at 20:02
  • You tagged this with C++11, C++14, C++17, C++20. For which standard are you asking? – bitmask May 31 '20 at 20:08
  • @cigien It works for me even with `-std=c++11` so either it was also supported in C++11 or it's one of those fun situations where they made the C++17 change in the headers already but didn't restrict it to C++ >= 17. – cdhowie May 31 '20 at 20:12
  • @cdhowie IIRC, pre-c++17 it compiled but was UB (or maybe IFNDR). But from c++17 wording was added to allow it. – cigien May 31 '20 at 20:14
  • @cigien Ah, gotcha. So probably worked everywhere already but the standard had to catch up... – cdhowie May 31 '20 at 20:15
  • @cdhowie correct, here's the [question](https://stackoverflow.com/questions/41913578/how-can-i-declare-a-member-vector-of-the-same-class/41913654#41913654) which has an answer that links to the change in c++17. – cigien May 31 '20 at 20:27

2 Answers2

5

It is true that, in general, it is invalid to instantiate templates from the standard library with arguments that are incomplete types:

[res.on.functions]/2 In particular, the effects are undefined in the following cases:
(2.5) — if an incomplete type (6.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.

However, Foo(Foo& f) is not an incomplete type, even if Foo is:

[basic.types]/5 A class that has been declared but not defined, an enumeration type in certain contexts (10.2), or an array of unknown bound or of incomplete element type, is an incompletely-defined object type. Incompletely-defined object types and cv void are incomplete types...

Foo(Foo& f) is a function type. It's not listed in this definition, so cannot possibly be incomplete.


std::vector is specifically allowed to be instantiated with an argument that is an incomplete type:

[vector.overview]/3 An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness requirements (20.5.3.5.1). T shall be complete before any member of the resulting specialization of vector is referenced.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
1

Per the Standard, this appears to be fine.

[res.on.functions]/2 says:

[...] [The] effects are undefined in the following cases:

  • [...]
  • If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.

But while a class type is incomplete within the immediate body of that class, a function type derived from that class type is not incomplete (since a function type is not an object type), much as a pointer to an incomplete class type is not an incomplete type. There are also no requirements within [func.wrap] that R or Args in std::function<R(Args...)> should be complete types.

More or less, this is equivalent to having a class data member of type Foo(*)(Foo&) (pointer to function), which is also fine.

You will need to be careful not to call the std::function data member within the immediate body of your class (e.g. in a data member initializer). (This is because a function call expression that returns an class prvalue will at some point require a temporary materialization conversion, which requires the class to be complete.) Calling it within member functions is fine, since Foo will be complete by that point, even for member functions defined inline.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366