Search code examples
c++variadic-templatesconstexpr

Two C++ compilers disagree on whether my 'count_args' function is constexpr


Here's a small function that counts the number of arguments you pass in:

#include <iostream>

constexpr static int count_args() {
    return 0;
}

template<typename... SS>
constexpr static int count_args(int &v, SS & ... restargs)
{
    constexpr int rest = count_args(restargs...);
    return rest + 1;
}

int main(int argc, char **argv) {
    int a,b;
    constexpr int count = count_args(a,b);
    std::cerr << "count: " << count << std::endl; // Intended to print 'count: 2'
}

Two different compilers give two different results for this. Under G++ 10.3.0, I get this error message:

> g++ -std=c++17 -o cx2.exe cx2.cpp
cx2.cpp: In instantiation of 'constexpr int count_args(int&, SS& ...) [with SS = {int}]':
cx2.cpp:15:16: error: 'restargs#0' is not a constant expression
   15 |  constexpr int rest = count_args(restargs...);

But under G++ 9.4.0, it compiles and runs as intended. Could somebody clarify which compiler is "doing the right thing?" Is this correct constexpr code, or is it invalid code?


Solution

  • From personal experience, older g++ compiler versions were generally sloppy at checking at if something was a constant expression. If something was optimized to become a constant even if it wasn't before, it was accepted without an error.

    This is not a constant expression in C++11 to C++20. The reason is that references have to be initialized by something usable in a constant expression (which function arguments are not). The rationale being that you don't know whether SS & ... restargs are all valid references. For example, if you called count_args like count_args(a, *static_cast<int*>(nullptr));, it shouldn't have been a constant expression.

    This is changed in C++23 by P2280R4, which neither clang nor gcc have implemented yet, as seen here: https://clang.llvm.org/cxx_status.html#cxx23 https://gcc.gnu.org/projects/cxx-status.html. This makes restargs.... "unknown references" that you can use in constant expressions, even though their initializer is not known.