5

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

Alex
  • 12,578
  • 15
  • 99
  • 195

3 Answers3

7

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

}
bolov
  • 72,283
  • 15
  • 145
  • 224
5

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

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
4

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.

1stCLord
  • 860
  • 5
  • 14
  • 2
    Thank you! Yes, I know it. But why does the C++ compiler makes it possible to declare a function as constexpr `reduce()` - if it never can not be constexpr? – Alex Aug 09 '16 at 12:54
  • 1
    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. – 1stCLord Aug 09 '16 at 12:56
  • 1
    @TheSombreroKid: that's the key point - maybe you should add it to your reply. – peterchen Aug 09 '16 at 13:11
  • 2
    @Alex To put it differently, the error occurs not when the template is declared, but when its instantiated with specific types. Both `std::accumulate` and reduce could have `constexpr` specializations for all types that are used. These specializations could appear after the declaration of the template, i.e. the compiler might not have seen these. – peterchen Aug 09 '16 at 13:12