问题
Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?
For example: http://melpon.org/wandbox/permlink/AGwniRNRbfmXfj8r
#include <iostream>
#include <functional>
#include <numeric>
#include <initializer_list>
template<typename Functor, typename T, size_t N>
T constexpr reduce(Functor f, T(&arr)[N]) {
return std::accumulate(std::next(std::begin(arr)), std::end(arr), *(std::begin(arr)), f);
}
template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
template<typename Functor, typename T, typename... Ts>
T constexpr reduce(Functor f, T t1, Ts... ts) {
return f(t1, reduce(f, std::initializer_list<T>({ts...})));
}
int constexpr constexpr_func() { return 2; }
template<int value>
void print_constexpr() { std::cout << value << std::endl; }
int main() {
std::cout << reduce(std::plus<int>(), 1, 2, 3, 4, 5, 6, 7) << std::endl; // 28
std::cout << reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7}) << std::endl;// 28
const int input[3] = {1, 2, 3}; // 6
std::cout << reduce(std::plus<int>(), input) << std::endl;
print_constexpr<5>(); // OK
print_constexpr<constexpr_func()>(); // OK
//print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
return 0;
}
Output:
28
28
6
5
2
Why error at this line: //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
even for C++14 and C++1z?
std::plus
-constexpr T operator()( const T& lhs, const T& rhs ) const;
(since C++14) - constexpr: http://en.cppreference.com/w/cpp/utility/functional/plusconstexpr initializer_list();
(since C++14) - construcot ofinitializer_list
is constexpr: http://en.cppreference.com/w/cpp/utility/initializer_list/initializer_list
Why does compiler allow to mark reduce()
as constexpr
, but reduce()
can't be used as template parameter even if all parameters passed to reduce()
known at compile-time?
The same effect for some compilers - which supported C++14 -std=c++14
:
- x86 GCC 7.0.0
-std=c++1z -O3
: http://melpon.org/wandbox/permlink/AGwniRNRbfmXfj8r - x86 gcc 4.9.2
-std=c++14 -O3
: https://godbolt.org/g/wmAaDT - x86 gcc 6.1
-std=c++14 -O3
: https://godbolt.org/g/WjJQE5 - x86 clang 3.5
-std=c++14 -O3
: https://godbolt.org/g/DSCpYv - x86 clang 3.8
-std=c++14 -O3
: https://godbolt.org/g/orSrgH - x86 Visual C++ - you should copy-paste code to: http://webcompiler.cloudapp.net/
- ARM gcc 4.8.2, ARM64 gcc 4.8, PowerPC gcc 4.8, AVR gcc 4.5.3 - doesn't support C+14
-std=c++14
For all these cases compile OK, until unused line: //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
回答1:
Let's go straight from it's proposal, www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf in section 4.1, third paragraph: and I quote:
A constant-expression function may be called with non-constant expressions, in that case there is no requirement that the resulting value be evaluated at compile time.
See this question: When does a constexpr function get evaluated at compile time?
template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
Again, as you know, std::accumulate isn't a constexpr
function.
template<int value>
void print_constexpr() { std::cout << value << std::endl; }
Again, as you know, non-type template arguments must be constant expressions.
Now:
template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
As to why it works: Here's what the C++ standard has to say:
[dcl.constexpr/6] (emphasis mine):
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a
constexpr
function orconstexpr
constructor, even though a call to such a function cannot appear in a constant expression ...
Note: that
A function instantiated from a function template is called a function template specialization;
When its not a template, it will fail:
int constexpr reduce(int(*f)(int, int), std::initializer_list<int> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
The compiler will complain now that you cannot call a non-constexpr
function in a function defined as constexpr
回答2:
Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?
It doesn't. But you are not defining a function constexpr
. You are defining a template.
Let's set up:
struct Is_constexpr {
constexpr Is_constexpr() = default;
constexpr auto bar() {
return 24;
}
};
struct Not_constexpr {
auto bar() {
return 24;
}
};
Now if you try to define a function (not a template) as constexpr that uses Not_constexpr
the compiler won't let you:
constexpr auto foo_function(Not_constexpr v)
{
return v.bar();
// error: call to non-constexpr function 'auto Not_constexpr::bar()'
}
You are however defining a template. Let's see how this goes:
template <class T>
constexpr auto foo(T v)
{
return v.bar();
}
The compiler lets you do this. No error. Why? Because it's a template. Some instantiations may be constexpr
, some not, depending on T
:
int main() {
constexpr Is_constexpr is_c;
constexpr Not_constexpr not_c;
std::integral_constant<int, foo(is_c)> a; // OK
//std::integral_constant<int, foo(not_c)> a; // ERROR
}
回答3:
If you do write this code:
constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7});
you will see that reduce doesn't not produce a constexpr result.
The reason is because "note: non-constexpr function 'accumulate >' cannot be used in a constant expression" And as you can see here - http://en.cppreference.com/w/cpp/algorithm/accumulate
std::accumulate is not constexpr
EDIT extending to answer the actual question, thanks @peterchen: It's compiled when it hits the usage, it doesn't and couldn't try and resolve the function until it compiles the specific version of the template. When it hits the usage and triggers the compile, it resolves the accumulate and sees it's not constexpr, so issues an error.
来源:https://stackoverflow.com/questions/38850730/why-does-the-c-compiler-makes-it-possible-to-declare-a-function-as-constexpr