6

The following small example of C++20 code gives a segmentation fault when run. Why?

If I create an object of class Implementation and call the consteval function implementation.foo() it returns 42 as expected. However if I create a reference of type Interface and call the consteval function interface.foo() I get a segmentation fault. I am missing something in why this would happen.

// Compile with
//
// g++ -std=c++20 -Werror -Wall -Wextra -Wpedantic consteval.cpp -o consteval

#include <iostream>

struct Interface
{
        virtual consteval int foo(void) = 0;
};

struct Implementation final : public Interface
{
        consteval int foo(void) override { return 42; }
};

int main(void)
{
        Implementation implementation;

        std::cout << "implementation.foo() returns: " << implementation.foo() << std::endl;

        Interface& interface = implementation;

        std::cout << "interface.foo() returns: " << interface.foo() << std::endl;

        return 0;
}

Link to Compiler Explorer

Anders
  • 63
  • 4
  • 4
    Polymorphism using virtual functions is a run-time only thing, while [`consteval`](https://en.cppreference.com/w/cpp/language/consteval) functions are required to produce a compile-time result. It's simply not possible to combine those two concepts. – Some programmer dude Dec 21 '22 at 21:32
  • 2
    Regarding the why, you can use a debugger to look where it crashed exactly. Clang does not accept this code with reasonable errors so I think this is gcc bug. – Quimby Dec 21 '22 at 21:32
  • 1
    Compiles without errors by gcc 12.2.1 – Sam Varshavchik Dec 21 '22 at 21:35
  • 2
    @Someprogrammerdude What? You can perfectly well have a `consteval virtual` function and call it polymorphically, *as long as all that still happens at compile time*. – HTNW Dec 21 '22 at 21:49
  • [https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) – Jesper Juhl Dec 21 '22 at 21:49

1 Answers1

3

Your code should not compile. I believe it is a bug in GCC that it tries to compile it and the weird segfault is just a consequence.

foo is consteval, so every usage of it must be part of a constant expression. But here you use it as interface.foo(), where interface is not a core constant expression (and foo is not static), so interface.foo() can't possibly be a constant expression. Thus your code is invalid and should simply fail with a compiler error (but should neither segfault nor "work" as it did in the comments; that's just GCC's fault).

If you correct the code to make the call valid, then you should get the right result. You could, say, wrap the thing in a constexpr function

consteval int example() {
   Implementation impl;
   Interface &intf = impl;
   return intf.foo();
}
int main() { std::cout << example() << "\n"; }

This works fine on current GCC.

Another way to get to correct code is to make the Implementation object constexpr static and foo const-qualified:

struct Interface {
        virtual consteval int foo(void) const = 0; // constexpr objects are const, so need const-qual to call foo
};

struct Implementation final : public Interface {
        consteval int foo(void) const override { return 42; }
};
int main() {
    constexpr static Implementation impl; // need static to form constant expression references ("the address of impl is now constexpr")
    Interface const &intf = impl; // this reference is automatically usable in constant expressions (being of reference type and constant-initialized), note that it is to a *const* Interface, requiring the change to foo
    std::cout << intf.foo() << "\n";
}

GCC still chokes on this one and produces a segfault, but Clang produces the expected result. Again, I suspect a GCC bug.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • This makes a lot of sense. I guess I just didn't expect a bug in GCC to be the reason for not initially seeing that my code is invalid C++20. – Anders Dec 22 '22 at 09:11