Почему компилятор C ++ позволяет объявить функцию как 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/AGwniRNRbfmXfj8rx86 gcc 4.9.2-std=c++14 -O3: https://godbolt.org/g/wmAaDTx86 gcc 6.1-std=c++14 -O3: https://godbolt.org/g/WjJQE5x86 clang 3.5-std=c++14 -O3: https://godbolt.org/g/DSCpYvx86 clang 3.8-std=c++14 -O3: https://godbolt.org/g/orSrgHx86 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

Для всех этих случаев скомпилируйте ОК, пока не будет использована неиспользуемая строка://print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error

 Torbjörn09 авг. 2016 г., 14:39
"компилятор C ++«Какой компилятор? GCC? Clang? MSVC? Какая версия?

Ответы на вопрос(3)

Почему компилятор C ++ позволяет объявить функцию как constexpr, которая не может быть constexpr?

Это не так. Но вы не определяете функциюconstexpr, Вы определяете шаблон.

Давайте настроим:

struct Is_constexpr {
  constexpr Is_constexpr() = default;
  constexpr auto bar() {
    return 24;
  }
};

struct Not_constexpr {
  auto bar() {
    return 24;
  }
};

Теперь, если вы попытаетесь определить функцию (не шаблон) как constexpr, который используетNot_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, третий абзац: и я цитирую:

Функция константного выражения может быть вызвана с неконстантными выражениями, в этом случае нет требования, чтобы результирующее значение оценивалось во время компиляции.

Смотрите этот вопрос:Когда функция constexpr оценивается во время компиляции?

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; }

Опять же, как вы знаете,нетиповые аргументы шаблона должны быть константными выражениями.

Сейчас:

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] (выделение мое):

Если конкретизированная специализация шаблона шаблона функции constexpr или функции-члена шаблона класса не сможет удовлетворитьтребования кconstexpr функция или конструктор constexpr, эта специализациявсе ещеconstexpr функция или жеconstexpr конструктор,хотя вызов такой функции не может появляться в константном выражении ...

Замечания:тот

Функция, созданная из шаблона функции, называется специализацией шаблона функции;

Когда это не шаблон, он потерпит неудачу:

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

Если вы пишете этот код:

  constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7});

вы увидите, что сокращение не приводит к результату constexpr.

Причина в том, что «примечание: не-constexpr функция« накапливать> »не может быть использована в постоянном выражении» И как вы можете видеть здесь -http://en.cppreference.com/w/cpp/algorithm/accumulate

STD :: накапливать не является constexpr

РЕДАКТИРОВАТЬ, расширяя, чтобы ответить на реальный вопрос, спасибо @peterchen: он скомпилирован, когда он попадает в использование, он не делает и не может попытаться разрешить функцию, пока не скомпилирует конкретную версию шаблона. Когда он попадает в использование и запускает компиляцию, он разрешает накопление и видит, что это не constexpr, поэтому выдает ошибку.

 The Sombrero Kid09 авг. 2016 г., 14:56
Он компилируется, когда он попадает в использование, он не может и не может попытаться разрешить функцию, пока не скомпилирует конкретную версию шаблона. Когда он попадает в использование и запускает компиляцию, он разрешает накопление и видит, что это не constexpr, поэтому выдает ошибку.
 peterchen09 авг. 2016 г., 15:11
@TheSombreroKid: это ключевой момент - возможно, вам следует добавить его в свой ответ.
 Alex09 авг. 2016 г., 14:54
Спасибо! Да, я знаю его. Но почему компилятор C ++ позволяет объявить функцию как constexpr?reduce() - если это никогда не может быть constexpr?
 peterchen09 авг. 2016 г., 15:12
@Alex Другими словами, ошибка возникает не при объявлении шаблона, а при его создании с конкретными типами. И то и другоеstd::accumulate и уменьшить мог иметьconstexpr специализации для всех типов, которые используются. Эти специализации могут появиться после объявления шаблона, то есть компилятор, возможно, не видел их.

Ваш ответ на вопрос