Как объявить constexpr extern?

Можно ли объявить переменную extern constexpr и определить это в другом файле?

Я пробовал, но компилятор выдает ошибку:

Декларация constexpr переменная 'iэто не определение

в.ч:

extern constexpr int i;

в.cpp:

constexpr int i = 10; 

7 ответов

Решение

Нет, вы не можете этого сделать, вот что говорит стандарт (раздел 7.1.5):

1 Спецификатор constexpr должен применяться только к определению переменной или шаблона переменной, объявлению функции или шаблона функции или объявлению статического члена данных литерального типа (3.9). Если какое-либо объявление функции, шаблона функции или шаблона переменной имеет спецификатор constexpr, то все его объявления должны содержать спецификатор constexpr. [Примечание: явная специализация может отличаться от объявления шаблона в отношении спецификатора constexpr. Параметры функции не могут быть объявлены constexpr. - конец примечания]

некоторые примеры, приведенные в стандарте:

  constexpr void square(int &x);  // OK: declaration
  constexpr int bufsz = 1024;  // OK: definition
  constexpr struct pixel {  // error: pixel is a type
    int x;
    int y;
    constexpr pixel(int);  // OK: declaration
  };

  extern constexpr int memsz; // error: not a definition

C++17 inline переменные

Эта удивительная особенность C++17 позволяет нам:

  • удобно использовать только один адрес памяти для каждой константы
  • хранить его как constexpr
  • сделать это в одной строке из одного заголовка

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Скомпилируйте и запустите:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub вверх по течению.

Стандарт C++ гарантирует, что адреса будут одинаковыми. Проект стандарта C++17 N4659 10.1.6 " Встроенный спецификатор":

6 Встроенная функция или переменная с внешней связью должны иметь одинаковый адрес во всех единицах перевода.

cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static не дано, то имеет внешнюю связь.

Смотрите также: Как работают встроенные переменные?

Вероятно, вам нужна инициализация extern и constexpr, например:

// in header
extern const int g_n;

// in cpp
constexpr int g_n = 2;

Это поддержка, хотя в Visual Studio 2017 только через режим соответствия:

Нет. Extern constexpr не имеет никакого смысла. Пожалуйста, прочитайте http://en.cppreference.com/w/cpp/language/constexpr

то есть бит "он должен быть немедленно создан или ему присвоено значение".

Сделать это можно, просто нужно заменить на в шапке:

      // declaration, possibly in header
extern const int i;

// source file
constexpr int i = 0;

Основная идея объектов заключается в следующем:

  • сделай это
  • инициализировать его постоянным выражением

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

Это действительно разрешено?!

Да. Давайте посмотрим на соответствующие разделы стандарта:

Два объявления сущностей объявляют одну и ту же сущность, если, [...] они соответствуют , имеют одну и ту же целевую область, которая не является областью действия функции или параметра шаблона, и либо

  • они появляются в одной единице перевода или [...]
  • они оба объявляют имена с внешней связью.

- [basic.link] §8

Проще говоря, обаiимеют одинаковое имя, поэтому они соответствуют, и они оба имеют внешнюю связь из-заextern.

Для любых двух объявлений сущности:

  • Если один объявляется переменной или функцией, другой должен объявитьEкак однотипный.
  • [...]

- [basic.link] §11

Возникает вопрос: являются ли две переменные одного и того же типа, если одна из них , а другая ?

Спецификатор, используемый в объявлении объекта, объявляет объект как . [...]

- [dcl.constexpr] §6

Ответ — да, он просто создает наш объект и никаким другим образом не меняет тип. Единственный остающийся вопрос заключается в том, разрешено ли нам делать одну декларацию, но нельзя делать другую:

Если какое-либо объявление функции или шаблона функции имеет илиconstevalспецификатор, то все его объявления должны содержать один и тот же спецификатор.

- [dcl.constexpr] §1

Нет, ограничения есть только для функций, а не для переменных. Разрешается подать одну декларациюconstи другие .

Это разрешено, но не является ли это совершенно бесполезным?

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

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

Примечание: не все компиляторы соответствуют стандарту по умолчанию. Использовать/Zc:externConstexprпри компиляции с MSVC.

Я согласен с вышесказанным, но есть следствие. Рассматривать:

ExternHeader.hpp

extern int e; // Must be extern and defined in .cpp otherwise it is a duplicate symbol.

ExternHeader.cpp

#include "ExternHeader.hpp"
int e = 0;

ConstexprHeader.hpp

int constexpr c = 0; // Must be defined in header since constexpr must be initialized.

Include1.hpp

void print1();

Include1.cpp

#include "Include1.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print1() {
    std::cout << "1: extern = " << &e << ", constexpr = " << &c << "\n";
}

Include2.hpp

void print2();

Include2.cpp

#include "Include2.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print2() {
    std::cout << "2: extern = " << &e << ", constexpr = " << &c << "\n";
}

main.cpp

#include <iostream>
#include "Include1.hpp"
#include "Include2.hpp"

int main(int argc, const char * argv[]) {
    print1();
    print2();
    return 0;
}

Какие отпечатки:

1: extern = 0x1000020a8, constexpr = 0x100001ed0
2: extern = 0x1000020a8, constexpr = 0x100001ed4

IE constexpr выделяется дважды, тогда как extern выделяется один раз. Это противоречит мне, так как я ожидаю constexpr быть более оптимизированным, чем extern,

Редактировать: const а также constexpr имеют одинаковое поведение в отношении распределения, поэтому с этой точки зрения поведение является ожидаемым. Хотя, как я уже сказал, я был удивлен, когда наткнулся на поведение constexpr,

Да, это несколько...

//===================================================================
// afile.h

#ifndef AFILE
#define AFILE

#include <cstddef>
#include <iostream>

enum class IDs {

  id1,
  id2,
  id3,
  END

};

// This is the extern declaration of a **constexpr**, use simply **const**
extern const int ids[std::size_t(IDs::END)];

// These functions will demonstrate its usage

template<int id> void Foo() { std::cout << "I am " << id << std::endl; }

extern void Bar();

#endif // AFILE

//===================================================================
// afile.cpp

#include "afile.h"

// Here we define the consexpr. 
// It is **constexpr** in this unit and **const** in all other units
constexpr int ids[std::size_t(IDs::END)] = {

  int(IDs::id1),
  int(IDs::id2),
  int(IDs::id3)

};

// The Bar function demonstrates that ids is really constexpr
void Bar() {

  Foo<ids[0]      >();
  Foo<ids[1] + 123>();
  Foo<ids[2] / 2  >();

}

//===================================================================
// bfile.h

#ifndef BFILE
#define BFILE

// These functions will demonstrate usage of constexpr ids in an extern unit

extern void Baz();
extern void Qux();


#endif // BFILE

//===================================================================
// bfile.cpp

#include "afile.h"

// Baz demonstrates that ids is (or works as) an extern field
void Baz() {

  for (int i: ids) std::cout << i << ", ";
  std::cout << std::endl;

}

// Qux demonstrates that extern ids cannot work as constexpr, though
void Qux() {

#if 0 // changing me to non-0 gives you a compile-time error...

  Foo<ids[0]>();

#endif

  std::cout << "Qux: 'I don't see ids as consexpr, indeed.'" 
            << std::endl;

}

//===================================================================
// main.cpp

#include "afile.h"
#include "bfile.h"

int main(int , char **)
{

  Bar();
  Baz();
  Qux();

  return 0;
}
Другие вопросы по тегам