Question on constexpr

I'm playing with this code created by Scott Schurr and presented at C++Now:

http://cppnow.org/session/cpp11-new-tools-for-class-and-library-authors/

#include <cstddef>
#include <stdexcept>

class str_const
{
    const char* const p_;
    const std::size_t sz_;
public:
    template<std::size_t N>
        constexpr str_const(const char(&a)[N]) :
           p_(a), sz_(N-1) {}

    constexpr char operator[](std::size_t n)
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("");
    }

    constexpr std::size_t size() { return sz_; }
};

template <class T = std::size_t>
constexpr
inline
T
binary_const(str_const b, std::size_t n = 0, T x = 0)
{
    return n == b.size() ? x :
           b[n] == ',' ? binary_const<T>(b, n+1, x) :
           b[n] == ' ' ? binary_const<T>(b, n+1, x) :
           b[n] == '0' ? binary_const<T>(b, n+1, x*2) :
           b[n] == '1' ? binary_const<T>(b, n+1, x*2+1) :
           throw std::domain_error("Only '0', '1', ',', and ' ' may be used");
}

int main()
{
    constexpr str_const test("Hi Mom!");
    static_assert(test.size() == 7, "");
    static_assert(test[6] == '!', "");
    constexpr unsigned i = binary_const("1111,0000");
    return i;
}

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp -S

Everything compiles and runs great (I know, I'm never happy). But when I inspect the assembly of main I see:

  callq __Z12binary_constImET_9str_constmS0_
  movl $240, %eax
  popq %rbp
  ret

Now returning 240 is the right answer. But why is binary_const being called at run time? Can't this call be elided because we know it is a constexpr function (without side effects) and that it is going to return the value 240 that we've already computed at compile time?

Howard

This is PR12848. Clang’s behavior here is conforming, but is obviously not ideal. The call certainly can be elided.

Quoting Richard Smith <richard@metafoo.co.uk>:

I'm playing with this code created by Scott Schurr and presented at C++Now:

http://cppnow.org/session/cpp11-new-tools-for-class-and-library-authors/

#include <cstddef>
#include <stdexcept>

class str_const
{
    const char* const p_;
    const std::size_t sz_;
public:
    template<std::size_t N>
        constexpr str_const(const char(&a)[N]) :
           p_(a), sz_(N-1) {}

    constexpr char operator[](std::size_t n)
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("");
    }

    constexpr std::size_t size() { return sz_; }
};

template <class T = std::size_t>
constexpr
inline
T
binary_const(str_const b, std::size_t n = 0, T x = 0)
{
    return n == b.size() ? x :
           b[n] == ',' ? binary_const<T>(b, n+1, x) :
           b[n] == ' ' ? binary_const<T>(b, n+1, x) :
           b[n] == '0' ? binary_const<T>(b, n+1, x*2) :
           b[n] == '1' ? binary_const<T>(b, n+1, x*2+1) :
           throw std::domain_error("Only '0', '1', ',', and ' ' may be
used");
}

int main()
{
    constexpr str_const test("Hi Mom!");
    static_assert(test.size() == 7, "");
    static_assert(test[6] == '!', "");
    constexpr unsigned i = binary_const("1111,0000");
    return i;
}

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp -S

Everything compiles and runs great (I know, I'm never happy). But when I
inspect the assembly of main I see:

        callq __Z12binary_constImET_9str_constmS0_
        movl $240, %eax
        popq %rbp
        ret

Now returning 240 is the right answer. But why is binary_const being
called at run time? Can't this call be elided because we know it is a
constexpr function (without side effects) and that it is going to return
the value 240 that we've already computed at compile time?

This is PR12848. Clang's behavior here is conforming, but is obviously not
ideal. The call certainly can be elided.

FWIW, LLVM's optimizers cannot themselves remove the call to __Z12binary_constImET_9str_constmS0_, because that function is recursive. Right now the inliner bails out when it sees a recursive function.
The test in the PR you mentioned gets the call removed, though, since there are no recursive functions.

Nuno

Hello Howard,

What is the purpose of throwing an exception in a constexpr function?
Or rather… What are the implications of throwing in a constexpr function?

Thanks and Regards,
Fernando.

If you supply a non-constexpr argument that would cause the exception to be thrown, or if you assign the result to a non-constexpr object, then you will get the run time exception.

If you are using the function to initialize a constxpr object, then you must be supplying constexpr arguments as well.
And if you supply a constexpr argument that would cause the exception to be thrown (if the function were to be executed at run time), then you will get a compile-time failure along the lines of:

test.cpp:183:24: error: constexpr variable 'i' must be initialized by a constant expression
    constexpr unsigned i = binary_const("11*11,0000");
                       ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:175:12: note: subexpression not valid in a constant expression
           throw std::domain_error("Only '0', '1', ',', and ' ' may be used");
           ^
test.cpp:174:26: note: in call to 'binary_const({&"11*11,0000"[0], 10}, 2, 3)'
           b[n] == '1' ? binary_const<T>(b, n+1, x*2+1) :
                         ^
test.cpp:174:26: note: in call to 'binary_const({&"11*11,0000"[0], 10}, 1, 1)'
test.cpp:183:28: note: in call to 'binary_const({&"11*11,0000"[0], 10}, 0, 0)'
    constexpr unsigned i = binary_const("11*11,0000");
                           ^
1 error generated.

So the purpose is for error checking, which will happen either at run time, or at compile time, depending on how the function is used.

Howard

Howard, thank you very much for your explanation.
I thought that “throw” was the culprit.

Regards,
Fernando.