为什么 C++ 编译器可以将函数声明为 constexpr,而不能是 constexpr?
Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?
为什么 C++ 编译器可以将函数声明为 constexpr,而不能是 constexpr?
例如: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;
}
输出:
28
28
6
5
2
为什么在这一行出错://print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
即使对于 C++14 和 C++1z?
std::plus
- constexpr T operator()( const T& lhs, const T& rhs ) const;
(C++14 起) - constexpr: http://en.cppreference.com/w/cpp/utility/functional/plus
constexpr initializer_list();
(C++14 起) - initializer_list
的构造是 constexpr: http://en.cppreference.com/w/cpp/utility/initializer_list/initializer_list
为什么编译器允许将 reduce()
标记为 constexpr
,但 reduce()
不能用作模板参数,即使传递给 reduce()
的所有参数都已知编译时?
某些编译器的效果相同 - 支持 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++ - 您应该将代码复制粘贴到:http://webcompiler.cloudapp.net/
- ARM gcc 4.8.2、ARM64 gcc 4.8、PowerPC gcc 4.8、AVR gcc 4.5.3 - 不支持 C+14
-std=c++14
对于所有这些情况编译OK,直到未使用的行://print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
如果您编写此代码:
constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7});
您将看到 reduce 不会产生 constexpr 结果。
原因是因为
"note: non-constexpr function 'accumulate >' cannot be used in a constant expression"
正如你在这里看到的 - http://en.cppreference.com/w/cpp/algorithm/accumulate
std::accumulate 不是 constexpr
编辑扩展以回答实际问题,感谢@peterchen:
它在使用时编译,在编译特定版本的模板之前,它不会也不能尝试解析函数。当它命中 usage 并触发编译时,它解析 accumulate 并发现它不是 constexpr,因此发出错误。
Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?
没有。但是你没有定义一个函数constexpr
。您正在定义模板。
让我们设置:
struct Is_constexpr {
constexpr Is_constexpr() = default;
constexpr auto bar() {
return 24;
}
};
struct Not_constexpr {
auto bar() {
return 24;
}
};
现在,如果您尝试将函数(不是模板)定义为使用 Not_constexpr
的 constexpr,编译器将不允许您:
constexpr auto foo_function(Not_constexpr v)
{
return v.bar();
// error: call to non-constexpr function 'auto Not_constexpr::bar()'
}
然而,您正在定义一个模板。让我们看看情况如何:
template <class T>
constexpr auto foo(T v)
{
return v.bar();
}
编译器允许您这样做。没有错误。为什么?因为它是模板。有些实例化可能是 constexpr
,有些则不是,具体取决于 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
}
让我们直接从它的提案开始,
www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf 第 4.1 节第三段:我引用:
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.
看到这个问题: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);
}
同样,如您所知,std::accumulate
不是 constexpr
函数。
template<int value>
void print_constexpr() { std::cout << value << std::endl; }
同样,如您所知,non-type template arguments must be constant expressions。
现在:
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);
}
至于它为什么起作用:这是 C++ 标准必须说的:
[dcl.constexpr/6](强调我的):
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 or constexpr
constructor, even though a call to such a function cannot appear in
a constant expression ...
注:that
A function instantiated from a function template is called a function
template specialization;
当它不是模板时,它会失败:
int constexpr reduce(int(*f)(int, int), std::initializer_list<int> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
编译器现在会抱怨您不能在定义为 constexpr
的函数中调用非 constexpr
函数
为什么 C++ 编译器可以将函数声明为 constexpr,而不能是 constexpr?
例如: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;
}
输出:
28
28
6
5
2
为什么在这一行出错://print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
即使对于 C++14 和 C++1z?
std::plus
-constexpr T operator()( const T& lhs, const T& rhs ) const;
(C++14 起) - constexpr: http://en.cppreference.com/w/cpp/utility/functional/plusconstexpr initializer_list();
(C++14 起) -initializer_list
的构造是 constexpr: http://en.cppreference.com/w/cpp/utility/initializer_list/initializer_list
为什么编译器允许将 reduce()
标记为 constexpr
,但 reduce()
不能用作模板参数,即使传递给 reduce()
的所有参数都已知编译时?
某些编译器的效果相同 - 支持 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++ - 您应该将代码复制粘贴到:http://webcompiler.cloudapp.net/
- ARM gcc 4.8.2、ARM64 gcc 4.8、PowerPC gcc 4.8、AVR gcc 4.5.3 - 不支持 C+14
-std=c++14
对于所有这些情况编译OK,直到未使用的行://print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error
如果您编写此代码:
constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7});
您将看到 reduce 不会产生 constexpr 结果。
原因是因为 "note: non-constexpr function 'accumulate >' cannot be used in a constant expression" 正如你在这里看到的 - http://en.cppreference.com/w/cpp/algorithm/accumulate
std::accumulate 不是 constexpr
编辑扩展以回答实际问题,感谢@peterchen: 它在使用时编译,在编译特定版本的模板之前,它不会也不能尝试解析函数。当它命中 usage 并触发编译时,它解析 accumulate 并发现它不是 constexpr,因此发出错误。
Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?
没有。但是你没有定义一个函数constexpr
。您正在定义模板。
让我们设置:
struct Is_constexpr {
constexpr Is_constexpr() = default;
constexpr auto bar() {
return 24;
}
};
struct Not_constexpr {
auto bar() {
return 24;
}
};
现在,如果您尝试将函数(不是模板)定义为使用 Not_constexpr
的 constexpr,编译器将不允许您:
constexpr auto foo_function(Not_constexpr v)
{
return v.bar();
// error: call to non-constexpr function 'auto Not_constexpr::bar()'
}
然而,您正在定义一个模板。让我们看看情况如何:
template <class T>
constexpr auto foo(T v)
{
return v.bar();
}
编译器允许您这样做。没有错误。为什么?因为它是模板。有些实例化可能是 constexpr
,有些则不是,具体取决于 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
}
让我们直接从它的提案开始, www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf 第 4.1 节第三段:我引用:
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.
看到这个问题: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);
}
同样,如您所知,std::accumulate
不是 constexpr
函数。
template<int value>
void print_constexpr() { std::cout << value << std::endl; }
同样,如您所知,non-type template arguments must be constant expressions。
现在:
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);
}
至于它为什么起作用:这是 C++ 标准必须说的:
[dcl.constexpr/6](强调我的):
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 aconstexpr
function orconstexpr
constructor, even though a call to such a function cannot appear in a constant expression ...
注:that
A function instantiated from a function template is called a function template specialization;
当它不是模板时,它会失败:
int constexpr reduce(int(*f)(int, int), std::initializer_list<int> il) {
return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}
编译器现在会抱怨您不能在定义为 constexpr
constexpr
函数