Действительно ли необходим constexpr?
Я смотрел новую функцию constexpr
на С++, и я не совсем понимаю ее необходимость.
Например, следующий код:
constexpr int MaxSize()
{
...
return ...;
}
void foo()
{
int vec[MaxSize()];
}
можно заменить на:
int MaxSize()
{
...
return ...;
}
static const int s_maxSize = MaxSize();
foo()
{
int vec[s_maxSize];
}
Обновление
Второй пример на самом деле не стандартный ISO С++ (благодаря нескольким пользователям для указания этого), но некоторые компиляторы (например, gcc) поддерживают его. Таким образом, это не const
, что делает программу действительной, но тот факт, что gcc поддерживает эту нестандартную функцию. (Насколько мне известно, это возможно только тогда, когда массив определяется как локальный для функции или метода, поскольку размер глобального массива должен быть известен во время компиляции.) Если я скомпилирую без параметров -std=c++98 -pedantic-errors
, даже код
int MaxSize()
{
return 10;
}
void foo()
{
int vec[MaxSize()];
}
будет компилироваться с помощью gcc.
Итак, я постараюсь перефразировать мой вопрос, принимая во внимание отзывы, которые пришли до сих пор (а также некоторое дополнительное чтение, которое я сделал за это время).
Я использую ключевое слово const
. С помощью const
я могу определить константу, которая имеет определенное значение за все время жизни. Константу можно инициализировать любым выражением, которое оценивается один раз, а именно при создании константы. Для этих случаев я считаю, что constexpr
довольно бесполезен: он вводит очень небольшую оптимизацию в том, что выражение, определяющее значение константы, будет вычисляться во время компиляции вместо времени выполнения. Каждый раз, когда мне нужна постоянная времени выполнения со сложной инициализацией, я использую ключевое слово const
.
Так что constexpr
может пригодиться в ситуациях, когда нам нужно инициализировать константу во время компиляции. Одним из примеров является векторное определение: стандарт не поддерживает определение размера во время выполнения. Другим примером является шаблон с одним или несколькими непиковыми параметрами.
В таких случаях я обычно использую макросы:
#define MAX_SIZE (10)
void foo()
{
int vec[MAX_SIZE];
}
но, если я правильно понимаю, функции constexpr
более мощные, чем макросы, поскольку они позволяют рекурсивные вызовы функций constexpr
в их определении. Однако я не могу придумать никакого практического приложения, в котором я когда-либо хотел использовать такое сложное вычисление для определения константы времени компиляции.
Итак, даже если это может быть интересной особенностью, я все же задаюсь вопросом, нужно ли это (то есть, как часто он может решать ситуации, когда макросов недостаточно). Возможно, посмотрите на несколько реальных примеров, которые не могут быть решены с помощью макросов, поможет мне изменить это мнение.
Ответы
Ответ 1
int vec[s_maxSize];
фактически является незаконным во втором примере, поэтому это невозможно сделать на С++. Но ваш первый пример - совершенно законный С++ 0x.
Итак, вот ваш ответ. Вы не можете делать то, что вы предлагаете на С++. Это можно сделать только в С++ 0x с constexpr
.
Я также хотел бы отметить, что этот код также работает в С++ 0x. Для этого в С++ потребуются некоторые действительно причудливые шаблоны классов.
constexpr unsigned int gcd(unsigned int const a, unsigned int const b)
{
return (a < b) ? gcd(b, a) : ((a % b == 0) ? b : gcd(b, a % b));
}
char vec[gcd(30, 162)];
Конечно, в С++ 0x вам все равно придется использовать тернарный оператор вместо операторов if. Но это работает и все еще намного легче понять, чем версия шаблона, которую вы бы использовали в С++:
template <unsigned int a, unsigned int b>
class gcdT {
public:
static unsigned int const value = gcdT<b, a % b>::value;
};
template <unsigned int a>
class gcdT<a, 0> {
public:
static unsigned int const value = a;
};
char vec[gcdT<30, 162>::value];
И тогда, конечно, на С++ вам все равно придется писать функцию gcd
, если вам нужно вычислить вещи во время выполнения, потому что шаблон не может использоваться с аргументами, которые различаются во время выполнения. И С++ 0x получит дополнительную оптимизацию, зная, что результат функции полностью определяется переданными в аргументах, что является фактом, который может быть выражен только с расширениями компилятора в С++.
Ответ 2
Что-то, что вы можете сделать с помощью constexpr, с которым вы не можете справиться с макросами или шаблонами, - это синтаксический анализ/обработка строк во время компиляции: Компиляция временной строки (изменение случая, сортировка и т.д.) с constexpr. Как небольшая выдержка из предыдущей ссылки, constexpr позволяет писать код, например:
#include "my_constexpr_string.h"
int main()
{
using namespace hel;
#define SDUMP(...) static_assert(__VA_ARGS__, "")
SDUMP(tail("abc") == "bc");
SDUMP( append("abc", "efgh") == "abcefgh" );
SDUMP( prepend("abc", "efgh") == "efghabc" );
SDUMP( extract<1,3>("help") == "el" );
SDUMP( insert<1>("jim", "abc") == "jabcim" );
SDUMP( remove("zabzbcdzaz", 'z') == "abbcdazzzz" );
SDUMP( erase("z12z34z5z", 'z') == "12345" );
SDUMP( map("abc", ToUpper()) == "ABC" );
SDUMP( find("0123456777a", '7') == 7 );
SDUMP( isort("03217645") == "01234567");
}
В качестве примера, когда это может быть полезно, это может облегчить вычисление/построение времени компиляции определенных парсеров и машин конечного состояния с регулярным выражением, которые заданы с литеральными строками. И чем больше обработки вы можете оттолкнуть, чтобы скомпилировать время, тем меньше обработка вы выполняете во время выполнения.
Ответ 3
int MaxSize() {
...
return ...; }
static const int s_maxSize = MaxSize();
int vec[s_maxSize];
Нет, не может. Это не законный С++ 03. У вас есть расширение компилятора, которое может выделять массивы переменной длины.
Ответ 4
constexpr
позволяет работать следующим образом:
#include<iostream>
using namespace std;
constexpr int n_constexpr() { return 3; }
int n_NOTconstexpr() { return 3; }
template<size_t n>
struct Array { typedef int type[n]; };
typedef Array<n_constexpr()>::type vec_t1;
typedef Array<n_NOTconstexpr()>::type vec_t2; // fails because it not a constant-expression
static const int s_maxSize = n_NOTconstexpr();
typedef Array<s_maxSize>::type vec_t3; // fails because it not a constant-expression
Аргументы template
действительно должны быть константными выражениями. Единственная причина, по которой работает ваш пример, - это переменные длины массивов (VLA) - функция, которая не находится в стандартном С++, но может быть во многих компиляторах в качестве расширения.
Более интересным может быть вопрос: почему бы не поставить constexpr
на каждую (const) функцию? Это наносит вред!?
Ответ 5
Другой опрятный трюк, который constexpr позволяет обнаруживать undefined поведение во время компиляции, который выглядит очень полезным инструментом. В следующем примере, взятом из связанного с вопросом вопроса, используется SFINAE для определения того, может ли добавление вызвать переполнение:
#include <iostream>
#include <limits>
template <typename T1, typename T2>
struct addIsDefined
{
template <T1 t1, T2 t2>
static constexpr bool isDefined()
{
return isDefinedHelper<t1,t2>(0) ;
}
template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
static constexpr bool isDefinedHelper(int)
{
return true ;
}
template <T1 t1, T2 t2>
static constexpr bool isDefinedHelper(...)
{
return false ;
}
};
int main()
{
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}
в результате чего (см. его в прямом эфире):
true
false
true
Ответ 6
По этому рассуждению вам не нужны константы вообще, даже не #define
. Нет встроенных функций или чего-либо еще.
Точка constexpr
, как и многие ключевые слова, позволяет вам лучше выразить свое намерение, чтобы компилятор понимал именно то, что вам нужно, а не только то, что вы ему рассказываете, поэтому он может улучшить оптимизацию для вас сцены.
В этом примере он позволяет писать поддерживаемые функции для вычисления размеров векторов вместо простого текста, который вы копируете и вставляете снова и снова.