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

1
  • 3
    "the C++ compiler"? Which compiler? GCC? Clang? MSVC? What version? Commented Aug 9, 2016 at 12:39

3 Answers 3

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

}
Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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.

4 Comments

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?
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.
@TheSombreroKid: that's the key point - maybe you should add it to your reply.
@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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.