Что-то вроде "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 будет/было бы хорошей причиной, чтобы поднять этот вопрос еще раз...