3

I am attempting to write a linked list of pointer-to-member-functions using constexpr. Mostly for fun but it may have a useful application.

struct Foo;

using MethodPtr = void (Foo::*)();

struct Node
{
    constexpr Node(MethodPtr method, const Node* next)
        : Method(method)
        , Next(next)
    {}

    constexpr Node Push(MethodPtr method)
    {
        return Node(method, this);
    }

    MethodPtr Method;
    const Node* Next;
};

struct Foo
{
    constexpr static Node GetMethods()
    {
        return Node{&Foo::Method1, nullptr}
            .Push(&Foo::Method2)
            .Push(&Foo::Method3);
    }

    void Method1() {}
    void Method2() {}
    void Method3() {}
};

int main(void)
{
    constexpr Node node = Foo::GetMethods();
}

The above code gives me the following error in main on the call to GetMethods():

const Node{MethodPtr{Foo::Method3, 0}, ((const Node*)(& Node{MethodPtr{Foo::Method2, 0}, ((const Node*)(& Node{MethodPtr{Foo::Method1, 0}, 0u}))}))}' is not a constant expression

Will someone please explain why this is not a constant expression? Or is there an alternate/correct way to achieve the goal of building a list of PTMFs at compile time?

EDIT: I am using the C++ compiler from avr-gcc 4.9.2. I will try this code on another compiler.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Osuvaldo
  • 274
  • 3
  • 10
  • 1
    Useing g++ 5.3.1 I get `a.cpp:37:43: error: 'Node{MethodPtr{Foo::Method3, 0}, ((const Node*)(&))}' is not a constant expression` – Jerry Jeremiah Feb 22 '16 at 01:34
  • The pointer is dangling, since all the temporaries created in `GetMethods` are destroyed at the `;`. – T.C. Feb 22 '16 at 02:21

4 Answers4

7

You are storing addresses of non-static-storage-duration temporaries, which is not allowed in constant expressions. The current version of this rule is in [expr.const]/5 (emphasis mine):

A constant expression is either a glvalue core constant expression whose value refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value is an object where, for that object and its subobjects:

  • each non-static data member of reference type refers to an entity that is a permitted result of a constant expression, and

  • if the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a function, or a null pointer value.

(C++11 contains similar rules (via the definition of address constant expression), but the constant expression rules were changed by multiple DRs before it was replaced by C++14 generalized constexpr, and I'm not really feeling like doing standard archaeology today.)

In fact, since every temporary Node created in GetMethods() except for the Node that got returned is destroyed at the ;, the Node returned would contain a dangling pointer.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Thanks for the concise and correct answer! Is there another approach to achieve my goal here? – Osuvaldo Feb 22 '16 at 03:16
  • @Osuvaldo Why do you need a linked list anyway? – T.C. Feb 22 '16 at 18:27
  • I don't exactly remember why I was doing this anymore and I'm not sure why I'm here now. However, I believe it may have had something to do with declaring a "statically dynamic sized array" of PTFM's to traverse. I'm pretty sure I wanted to use it for a series of tests that would be run on an embedded environment. – Osuvaldo Jun 11 '17 at 14:52
  • Hi T.C. Please, can you _add_ a real answer to [this question](https://stackoverflow.com/a/73767506/19495502)? – mada Sep 19 '22 at 17:24
0

You want to get the pointers to Foo's methods, but Foo is a class, and you could call this in a derived class (where the methods are different).

vonbrand
  • 11,412
  • 8
  • 32
  • 52
  • I don't exactly understand your answer. Sure, a derived class that inherits publicly could hide the base methods. I understand and expect `GetMethods()` to return a linked list of methods of `Foo`. – Osuvaldo Feb 22 '16 at 01:42
  • @Osuvaldo if you want a list of the member functions for base classes, you'll have to build it somehow. – vonbrand Feb 22 '16 at 21:05
0

There are bugs in this area with gcc. See this link

I've checking std::is_literal_type< Node >() in Visual Studio 2013 and 2015 and I get different answers. Clearly, regardless of the standard, this is still a dark corner of the language...

You have actually taken the address of a temporary and placed it into the list. So, even if you could do this in a const Expr, the Node * points to an invalid object. (You only have one Node object that survives the expression, but you have pointers to two more.)

However, you'll still have problems with a later version because your structure is not a LiteralType.

Rob L
  • 2,351
  • 13
  • 23
0

You are returning a pointer to a temparary variable that goes out of scope.

Pointers to temporary objects are clearly not compile time constant values. Dangling pointers doubly so.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    "Pointers to temporary objects are clearly not compile time constant values." Nitpick: `static constexpr const int& i = 5; static constexpr const int* p= &i; /* OK */` – T.C. Feb 22 '16 at 02:45
  • @t.c. that "temporary" object is rather non-temporary. ;) – Yakk - Adam Nevraumont Feb 22 '16 at 02:53
  • @zenith inside the struct. It contains pointers to other structs, all but the last one are temporary objects. `Push` creates the pointer to the previous one, returning a new temporary. – Yakk - Adam Nevraumont Feb 22 '16 at 03:10