Что-то вроде "if constexpr", но для определения класса
if constexpr
- большой шаг для избавления от препроцессора в программах на С++. Однако он работает только в функциях, как в этом примере:
enum class OS
{
Linux,
MacOs,
MsWindows,
Unknown
};
#if defined(__APPLE__)
constexpr OS os = OS::MacOs;
#elif defined(__MINGW32__)
constexpr OS os = OS::MsWindows;
#elif defined(__linux__)
constexpr OS os = OS::Linux;
#else
constexpr OS os = OS::Unknown;
#endif
void printSystem()
{
if constexpr (os == OS::Linux)
{
std::cout << "Linux";
}
else if constexpr (os == OS::MacOs)
{
std::cout << "MacOS";
}
else if constexpr (os == OS::MsWindows)
{
std::cout << "MS Windows";
}
else
{
std::cout << "Unknown-OS";
}
}
Но мечты об избавлении от препроцессора не совсем удовлетворены - потому что следующие примеры не компилируются:
1 Невозможно использовать его в определении класса для определения некоторых членов класса по-разному:
class OsProperties
{
public:
static void printName()
{
std::cout << osName;
}
private:
if constexpr (os == OS::Linux)
{
const char* const osName = "Linux";
}
else if constexpr (os == OS::MacOs)
{
const char* const osName = "MacOS";
}
else if constexpr (os == OS::MsWindows)
{
const char* const osName = "MS Windows";
}
else
{
const char* const osName = "Unknown";
}
};
2 И это не работает для не класса:
if constexpr (os == OS::Linux)
{
const char* const osName = "Linux";
}
else if constexpr (os == OS::MacOs)
{
const char* const osName = "MacOS";
}
else if constexpr (os == OS::MsWindows)
{
const char* const osName = "MS Windows";
}
else
{
const char* const osName = "Unknown";
}
Я (почти) уверен, что это соответствует спецификации С++ 17, что if constexpr
работает только внутри тел функции, но мои вопросы:
Q1 Как добиться аналогичного эффекта, например if-constexpr
в функциях - для класса и глобальной области действия в С++ 1z/С++ 14? И я не спрашиваю здесь еще одно объяснение специализации шаблонов... Но то, что имеет ту же простоту, что и if constexpr
...
Q2 Есть ли какой-либо план расширения С++ для вышеупомянутых областей?
Ответы
Ответ 1
Как добиться аналогичного эффекта, например if-constexpr в функциях - для класса и глобальной области в С++ 1z/С++ 14? И я не задаю здесь еще одно объяснение специализации шаблонов...
Вы в основном просто сказали: "Я хочу специализацию шаблона, но без этой досадной специализации шаблонов".
if constexpr
- это инструмент для изменения поведения функций, основанный на конструкциях времени компиляции. Специализация шаблона - это инструмент, который С++ обеспечивает изменение определений, основанное на конструкциях времени компиляции. Это единственный инструмент, который предоставляет С++ для этой функции.
Теперь для упрощенного случая инициализации переменной вы всегда можете создать и вызвать лямбду. С++ 17 предлагает поддержку constexpr
для lambdas, и лямбда сможет использовать if constexpr
, чтобы решить, какое значение вернуть.
Есть ли какой-либо план расширения С++ для вышеупомянутых областей?
Нет. Вот все предложения, и ни один из последних из последних двух лет не вникает в этот домен.
И это вряд ли когда-либо будет.
Ответ 2
Тип индекса:
template<std::size_t I>
using index = std::integral_constant<std::size_t, I>;
first_truth
берет набор переменных времени компиляции и говорит, что индекс первого во время компиляции. Если вы передадите ему N компиляций bools, он вернет N, если все ложны:
constexpr index<0> first_truth() { return {}; }
template<class...Rest>
constexpr index<0> first_truth(std::true_type, Rest...) { return {}; }
template<class...Rest>
constexpr auto first_truth(std::false_type, Rest...rest) {
return index<first_truth( rest... )+1>{};
}
dispatch
берет набор переменных времени компиляции и возвращает лямбда. Эта лямбда возвращается через совершенную пересылку первого элемента, который соответствует первому истинному времени компиляции bool:
template<class...Bools>
constexpr auto dispatch(Bools...bools) {
constexpr auto index = first_truth(bools...);
return [](auto&&...fs){
return std::get< decltype(index){} >(
std::forward_as_tuple( decltype(fs)(fs)... )
);
};
}
Время типа bool компиляции:
template<bool b>
using bool_t = std::integral_constant<bool, b>;
template<bool b>
bool_t<b> bool_k{};
Теперь мы решим вашу проблему:
const char* const osName =
dispatch(
bool_k<os == OS::Linux>,
bool_k<os == OS::MacOs>,
bool_k<os == OS::MsWindows>
)(
"Linux",
"MacOS",
"MS Windows",
"Unknown"
);
который должен аппроксимировать переключатель времени компиляции. Мы могли бы более тесно связать bools с аргументами с немного большей работой.
Код не скомпилирован, вероятно, содержит tpyos.
Ответ 3
как определить разные типы на основе некоторой постоянной времени компиляции без специализации шаблонов?
Вот он:
constexpr auto osPropsCreate()
{
if constexpr (os == OS::Linux) {
struct Props { const char* name; int props1; using handle = int; };
return Props{"linux", 3};
} else if constexpr (os == OS::MacOs) {
struct Props { const char* name; using handle = float; };
return Props{"mac"};
} else if constexpr (os == OS::MsWindows) {
struct Props { const char* name; using handle = int; };
return Props{"win"};
} else
return;
}
using OsProps = decltype(osPropsCreate());
constexpr OsProps osProps = osPropsCreate();
Как вы можете видеть - я использовал новую конструкцию if constexpr
для создания из некоторой функции "реализации" типа, зависящего от постоянной времени компиляции. Это не так просто использовать как static if
в языке D, но он работает - я могу это сделать:
int linuxSpecific[osProps.props1];
int main() {
std::cout << osProps.name << std::endl;
OsProps::handle systemSpecificHandle;
}
Далее - определить разные функции в зависимости от постоянной времени компиляции:
constexpr auto osGetNameCreate() {
if constexpr (os == OS::Linux) {
struct Definition {
static constexpr auto getName() {
return "linux";
}
};
return Definition::getName;
} else if constexpr (os == OS::MacOs) {
// we might use lambda as well
return [] { return "mac"; };
} else if constexpr (os == OS::MsWindows) {
struct Definition {
static constexpr auto getName() {
return "win";
}
};
} else
return;
}
constexpr auto osGetName = osGetNameCreate();
int main() {
std::cout << osGetName() << std::endl;
}
Фактически, они могут быть либо функциональными объектами (функторами), либо статическими функциями-членами из вложенных классов. Это не имеет значения - у каждого есть полная свобода определять разные вещи для разных констант времени компиляции (тип ОС в этом случае). Обратите внимание, что для неизвестной системы мы просто возвращаем void
- это вызовет ошибку компиляции для неизвестной системы...
Отвечая на второй вопрос:
Первый ответ дает аргументы в комментариях (ссылка). Моя интерпретация заключается в том, что стандартный комитет C++ не готов к этим изменениям. Возможно, состязание с D будет/было бы хорошей причиной, чтобы поднять этот вопрос еще раз...