Нужен ли constexpr? - PullRequest
       19

Нужен ли constexpr?

15 голосов
/ 16 мая 2011

Я смотрю на новую constexpr функцию C ++ и не до конца понимаю необходимость в ней.

Например, следующий код:

constexpr int MaxSize()
{
    ...

    return ...;
}

void foo()
{
    int vec[MaxSize()];
}

можно заменить на:

int MaxSize()
{
    ...

    return ...;
}

static const int s_maxSize = MaxSize();

foo()
{
    int vec[s_maxSize];
}

Обновление

Второй пример на самом деле не стандарт ISO C ++ (спасибо нескольким пользователям за указание на это), но некоторые компиляторы (например, 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 в своем определении. Однако я не могу представить себе никакого практического применения, в котором я когда-либо хотел использовать такие сложные вычисления для определения постоянной времени компиляции.

Итак, даже если это может быть интересной функцией, я все еще задаюсь вопросом, нужна ли она (то есть как часто она может разрешать ситуации, когда макросов недостаточно). Возможно, просмотр нескольких реальных примеров, которые невозможно решить с помощью макросов, поможет мне изменить это мнение.

Ответы [ 6 ]

18 голосов
/ 25 июня 2011

int vec[s_maxSize]; на самом деле недопустимо во втором примере, так что это невозможно сделать в C ++.Но ваш первый пример совершенно легален C ++ 0x.

Так что ваш ответ.На самом деле вы не можете делать то, что вы предлагаете в C ++.Это может быть сделано только в C ++ 0x с constexpr.

Я также хотел бы отметить, что этот код также работает в C ++ 0x.Выполнение этого в C ++ потребует некоторых действительно причудливых шаблонов классов.

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)];

Конечно, в C ++ 0x вам все равно придется использовать троичный оператор вместо операторов if.Но это работает и все еще намного проще для понимания, чем версия шаблона, которую вы будете вынуждены использовать в C ++:

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

И затем, конечно, в C ++ вам все равно придется писатьgcd функция, если вам нужно было что-то вычислять во время выполнения, потому что шаблон нельзя использовать с аргументами, которые меняются во время выполнения.И C ++ 0x будет иметь дополнительный импульс оптимизации, зная, что результат функции полностью определяется переданными аргументами, что можно выразить только с помощью расширений компилятора в C ++.

7 голосов
/ 24 июля 2011

То, что вы можете сделать с 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 голосов
/ 25 июня 2011
int MaxSize() {
    ...

    return ...; }

static const int s_maxSize = MaxSize();

int vec[s_maxSize];

Нет, не может. Это не законно C ++ 03. У вас есть расширение компилятора, которое может размещать массивы переменной длины.

0 голосов
/ 27 января 2014

Еще один полезный трюк, который constexpr позволяет обнаруживать неопределенное поведение во время компиляции, которое выглядит как очень полезный инструмент.В следующем примере, взятом из вопроса, который я связал, используется 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
0 голосов
/ 29 октября 2013

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's not a constant-expression

static const int s_maxSize = n_NOTconstexpr();
typedef Array<s_maxSize>::type vec_t3; // fails because it's not a constant-expression

template аргументы действительно должны быть выражениями-константами.Единственная причина, по которой ваш пример работает, заключается в том, что массивы переменной длины (VLA) - это функция, которая не входит в стандартный C ++, но может присутствовать во многих компиляторах как расширение.

Более интересный вопрос может быть: почему бы и нетпоставить constexpr на каждую (постоянную) функцию?Вредит ли это!?

0 голосов
/ 25 июня 2011

По этой причине вам вообще не нужны константы, даже #define.Никаких встроенных функций или чего-либо еще.

Смысл constexpr, как и многих других ключевых слов, состоит в том, чтобы позволить вам выражать свои намерения лучше, чтобы компилятор точно понимал, чего вы хотите вместо того, что вы говорите, так что он может сделать лучшую оптимизацию для вас за кулисами.

В этом примере он позволяет вам писать поддерживаемые функции для вычисления размеров вектора вместо простого текста, которыйВы копируете и вставляете снова и снова.

...