Alex Alex - 3 months ago 16
C++ Question

Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?

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?



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
:



For all these cases compile OK, until unused line:
//print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error

Answer

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 or constexpr 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

Comments