Search code examples
c++gccc++20c++-conceptsgcc9

Compilation error after upgrade to gcc 9 when using concepts with argument pack in if constexpr


I have a code that uses a concept with argument pack in if constexpr context. It used to compile and work in gcc 8 but breaks in gcc 9:

#include <utility>
#include <tuple>

template <typename T, typename ...Ts>
concept
#if __GNUC__ < 9
bool
#endif
is_handled = requires(T handler, Ts && ...args) {
    handler.handle(std::forward<Ts>(args)...);
};

template <typename T>
concept
#if __GNUC__ < 9
bool
#endif
has_name = requires(T) {
    T::name;
};

template <typename ...Handlers>
struct Dispatcher {

    template <typename ...Ts>
    void operator()(Ts && ...args) {
        std::apply(
            [&](auto && ...handlers) {
                (handle(handlers, std::forward<Ts>(args)...),...); 
            },
            m_handlers
        );
    }

private:
    template <typename Handler, typename ...Ts>
    void handle(Handler & handler, Ts && ...args) {
        if constexpr (is_handled<Handler, Ts...>) {
            handler.handle(std::forward<Ts>(args)...);
        }
    }

    template <typename Handler>
    char const* get_code() {
        if constexpr (has_name<Handler>) {
            return Handler::name;
        }
        return nullptr;
    }

    std::tuple<Handlers...> m_handlers;
};

The error produced by GCC 9.2 is:

<source>: In member function 'void Dispatcher<Handlers>::handle(Handler&, Ts&& ...)':
<source>:38:49: error: expected unqualified-id before ')' token
   38 |         if constexpr (is_handled<Handler, Ts...>) {
      |                                                 ^

See compiler explorer here. You can switch between gcc versions 8.3 and 9.2 to see the difference.


Solution

  • You are correct that it should compile and that it is a regression in GCC 9.1.

    There is already a bug report for this in GCC's bug tracker here.

    You can avoid the parser bug by placing additional parentheses around the if constexpr condition, forcing it to be parsed as expression instead of declaration:

    if constexpr ((is_handled<Handler, Ts...>)) {
        //...
    }