Search code examples
c++c++11language-lawyerconstexprstatic-assert

Why can't I get the argument count of a template function at compile-time?


#include <cstddef>

template<typename... Types>
constexpr std::size_t getArgCount(Types&&...) noexcept
{
    return sizeof...(Types);
}

struct A
{
    int n;

    void f()
    {
        static_assert(getArgCount(n) > 0); // not ok, why?
    }
};

int main()
{
    int n;
    static_assert(getArgCount(n) > 0); // ok
}

Why can't I get the argument count of a template function at compile-time?

error message:

1>test.cpp
1>test.cpp(17,45): error C2131:  expression did not evaluate to a constant
1>test.cpp(17,42): message :  failure was caused by a read of a variable outside its lifetime
1>test.cpp(17,42): message :  see usage of 'this'

Solution

  • Anything that accesses this outside constexpr context is not a constant expression, as defined in [expr.const]/2.1:

    An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

    • this, except in a constexpr function or a constexpr constructor that is being evaluated as part of e;

    (We need this to access n in order to pass it to getArgCount by reference)

    So that's why the first case doesn't compile.

    The second case compiles because it doesn't involve an lvalue-to-rvalue conversion of a non-constant (sizeof(n) does not actually "read" n).

    To demonstrate this, the following will also compile:

    struct A
    {
        int n;
    
        void f()
        {
            int m = n;
            static_assert(getArgCount(m) > 0); // ok, m doesn't need `this`
        }
    };
    

    Note: Having a reference inside a constexpr context (the Types&& part) by itself doesn't break "constexpr-ness" if the lifetime of the reference began within that context: [expr.const]/2.11.2.

    Another example:

    struct A
    {
        int n;
    
        void f()
        {
            static_assert(sizeof(n) > 0); // ok, don't need this for sizeof(A::n)
        }
    };
    

    The following won't compile:

        int n = 1;
        static_assert(getArgCount(n+1) > 0); // not ok, (n+1) "reads" n