While experimenting with constexpr functions and templates (and non-type template arguments), I stumbled upon a phenomenon, and I cannot understand which rule brings it into effect.
So my question essentially is "Why does this happen", according to the rules about constexpr-s. "this" is the following.
In one of the constexpr functions, if a parameter is used directly then there is no problem with this parameter being used in a compile-time computation. (example lines 2)
When the same parameter is used as an argument to another constexpr-function, then the compiler complaints that this expression (the parameter id) is not a constexpr. (example line 3)
In short:
template <typename T> constexpr std::size size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return std::make_index_sequence< obj.size() > { }.size(); } // OK ...
template <typename T> constexpr auto sz2 (T obj) { return std::make_index_sequence< size(obj) > { }.size(); } // ERROR
// "obj" is [suddenly] not a constexpr
This happens with both g++-4.9.1 and clang++-3.4.2 .
Below is a small test program for quick and easy experimentation.
#include <utility>
#include <array>
#include <iostream>
// utils
template <size_t N> using require_constexpr = std::make_index_sequence<N>;
template <typename...> constexpr void noop (void) { }
// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
int main0 (int, char**)
{
constexpr auto const ar = std::array<int, 4u> { 4, 5, 6, 7 };
// Check constexpr-computability of size(), sz1() and the expansion of sz2()
noop<
require_constexpr<
size(require_constexpr< ar.size() > { }) + sz1(ar) +
size(require_constexpr< size(ar) > { })
>
>();
// But this is an error
// ERROR: "obj" is not a constexpr in sz2()
//noop< require_constexpr< sz2(ar) > >();
return 0;
}
Edit Here is the relative compilation output.
clang
src/main1.cpp:12:87: error: non-type template argument is not a constant expression
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
^~~~~~~~~
src/main1.cpp:28:32: note: in instantiation of function template specialization 'sz2<std::array<int, 4> >' requested here
noop< require_constexpr< sz2(ar) > >();
^
src/main1.cpp:12:92: note: read of non-constexpr variable 'obj' is not allowed in a constant expression
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
^
src/main1.cpp:12:92: note: in call to 'array(obj)'
src/main1.cpp:12:49: note: declared here
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
^
gcc
src/main1.cpp: In substitution of ‘template<long unsigned int N> using require_constexpr = std::make_index_sequence<N> [with long unsigned int N = size<std::array<int, 4ul> >(obj)]’:
src/main1.cpp:12:102: required from ‘constexpr auto sz2(T) [with T = std::array<int, 4ul>]’
src/main1.cpp:28:38: required from here
src/main1.cpp:12:102: error: ‘obj’ is not a constant expression
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
^
src/main1.cpp:12:102: note: in template argument for type ‘long unsigned int’
Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors. constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time.
A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.
Constexpr functions are implicitly inline, which means they are suitable to be defined in header files. Like any function in a header, the compiler is more likely to inline it than other functions.
2) A function defined entirely inside a class/struct/union definition, whether it's a member function or a non-member friend function, is always inline. 3) A function declared constexpr is always inline.
This looks like a bug with how the two compilers treat compiler-generated copy constructors.
This code compiles using both clang and g++:
#include <utility>
// utils
template <std::size_t N> struct require_constexpr { constexpr std::size_t size() const { return N; } };
struct test {
constexpr std::size_t size() const { return 0; }
constexpr test() { }
constexpr test(const test &) { }
};
template <typename...> constexpr void noop (void) { }
// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
int main (int, char**)
{
constexpr auto const ar = test();
// Check constexpr-computability of size(), sz1() and the expansion of sz2()
noop<
require_constexpr<
size(require_constexpr< ar.size() > { }) + sz1(ar) +
size(require_constexpr< size(ar) > { })
>
>();
noop< require_constexpr< sz2(ar) > >();
return 0;
}
But if we change the line
constexpr test(const test &) { }
to
constexpr test(const test &) = default;
Then it compiles in neither (g++, clang), even though there is absolutely no difference between what the two constructors do (test
being a completely empty class), and §12.8 [class.copy]/p13 states that
If the implicitly-defined constructor would satisfy the requirements of a
constexpr
constructor (7.1.5), the implicitly-defined constructor isconstexpr
.
Furthermore, if the implicit copy constructor weren't constexpr
, then the explicit-default declaration with constexpr
should have caused the program to be ill-formed, with a diagnostic required (§8.4.2 [dcl.fct.def.default]/p2):
An explicitly-defaulted function may be declared
constexpr
only if it would have been implicitly declared asconstexpr
.
But both compilers (clang, g++) compile the second version of code if the second noop
call is commented out.
The key difference between sz1 and sz2 is that sz1 passes the address of obj to the size member function, which is not a valid result of a constant-expression but is fine as an intermediate result operand. sz2 performs an lvalue->rvalue conversion on obj for passing to the size function, and since obj is not constant this makes the expression non-constant.
T.C.'s point about implicit vs. explicit constructors is interesting. The source of the difference is that the implicit trivial copy constructor does a bitwise copy, which involves copying a (non-constant) byte of padding, whereas the user-provided copy constructor doesn't copy anything. But the standard says that the implicit constructor does a memberwise copy, so they ought to be treated the same.
What's not clear is whether they should both be rejected or both accepted; a strict reading of 5.19 suggests that both should be rejected, as both involve an lvalue->rvalue conversion for obj using the copy constructor. I've raised this issue with the C++ committee.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With