Как включить/включить несколько условий?

Есть ли способ разветвления на нескольких условиях без написания кода, который выглядит как беспорядок? Синтаксический сахар в С++ 11 или С++ 14 будет оценен.

#include <iostream>

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    // How not to:
    if (a < 0)
    {
        if (b == false)
        {
            if (str != "morning")
            {
                return STATE_1;
            }
            else
            {
                return STATE_2;
            }
        }
        else
        {
            if (str != "morning")
            {
                return STATE_3;
            }
            else
            {
                return STATE_4;
            }
        }
    }
    else // a >= 0
    {
        if (b == false)
        {
            if (str != "morning")
            {
                return STATE_5;
            }
            else
            {
                return STATE_6;
            }
        }
        else
        {
            if (str != "morning")
            {
                return STATE_7;
            }
            else
            {
                return STATE_8;
            }
        }
    }
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

Ответ 1

здесь моя версия:

Особенности:

  • Сохраняет компилятор для проверки отсутствующих случаев и дает информационные сообщения о том, какие случаи были пропущены.

  • Оценка времени выполнения компиляции означает нулевую служебную нагрузку

  • Нет макросов, чтобы загрязнять глобальное пространство имен и беспорядочно блокировать библиотеки только для заголовка: -)

Недостатки:

  • Требование предопределить некоторые шаблонные перечисления (только один раз, в библиотеке, которую я сделал для вас)

код:

#include <iostream>
#include <utility>
#include <sstream>
#include <string>

namespace detail{

    template<size_t N> struct boolean_value;
    template<size_t N> using boolean_value_t = typename boolean_value<N>::type;
    template<size_t N> constexpr auto to_int(boolean_value_t<N> b) { return static_cast<int>(b); };
    template<size_t N> constexpr auto to_boolean_value(int i) { return static_cast<boolean_value_t<N>>(i); };

    template<> struct boolean_value<1> {
        enum type { bit0, bit1 };
    };

    template<> struct boolean_value<2> {
        enum type { bit00, bit01, bit10, bit11 };
    };

    template<> struct boolean_value<3> {
        enum type { bit000, bit001, bit010, bit011, bit100, bit101, bit110, bit111 };
    };

    template<class...Args, size_t...Is>
    static constexpr auto make_bitfield(std::tuple<Args...> t, std::index_sequence<Is...>)
    {
#if __cplusplus > 201402L
        int accum = (0 | ... | (std::get<Is>(t) ? (1 << Is) : 0));
#else
        int accum = 0;
        using expand = int[];
        (void) expand { (std::get<Is>(t) ? accum |= (1 << Is) : 0) ... };
#endif
        return to_boolean_value<sizeof...(Is)>(accum);
    }

}

template<class...Args>
constexpr
auto mcase(Args&&...args)
{
    return detail::make_bitfield(std::make_tuple(bool(std::forward<Args>(args))...),
                                 std::index_sequence_for<Args...>());
}

// little function to defeat the optimiser, otherwise clang inlines the whole program!
auto get_result()
{
    using namespace std;

    istringstream ss("foo 2");
    auto result = tuple<string, int>();
    ss >> get<0>(result) >> get<1>(result);
    return result;
}

int main()
{
    using namespace std;
    const auto result = get_result();
    const auto& s1 = std::get<0>(result);
    const auto& v1 = std::get<1>(result);

    switch(mcase(s1 == "foo"s, v1 == 2))
    {
        case mcase(true, true):
            cout << mcase(true, true) << endl;
            break;

        case mcase(false, false):
            cout << mcase(false, false) << endl;
            break;
    }
    return 0;
}

Пример вывода компилятора:

./mswitch.cpp:114:12: warning: enumeration values 'bit01' and 'bit10' not handled in switch [-Wswitch]
    switch(mcase(s1 == "foo"s, v1 == 2))
           ^
1 warning generated.
./mswitch.cpp:114:12: warning: enumeration values 'bit01' and 'bit10' not handled in switch [-Wswitch]
    switch(mcase(s1 == "foo"s, v1 == 2))
           ^
1 warning generated.

Результат выполнения:

3

Ответ 2

Можно было встроить список логических (результаты условия) в POD во время компиляции и switch на нем.

Использование: main.cpp

#include <iostream> /* std::cout */
#include "mswitch.h" /* mswitch, mcase */

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    mswitch(a >= 0, b == true, str == "morning")
    {
        mcase(false, false, false): return STATE_1;
        mcase(false, false, true) : return STATE_2;
        mcase(false, true, false) : return STATE_3;
        mcase(false, true, true)  : return STATE_4;
        mcase(true, false, false) : return STATE_5;
        mcase(true, false, true)  : return STATE_6;
        mcase(true, true, false)  : return STATE_7;
        mcase(true, true, true)   : return STATE_8;
    }
    return STATE_1;
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

Синтаксический сахар: mswitch.h

#ifndef MSWITCH_GUARD_H
#define MSWITCH_GUARD_H

#include <initializer_list>
#include <cstddef>

namespace mswitch
{
    constexpr long long encode(long long value, size_t size) { return value << 6 | (0x3F & size); }

    class mswitch
    {
        std::initializer_list<bool> _flags;
    public:
        mswitch(std::initializer_list<bool> const& l) : _flags(l) {}
        operator long long() const
        {
            long long result = 0;
            size_t index = 0;
            for (bool b : _flags) {
                result |= b << index++;
            }
            return encode(result, _flags.size());
        }
    };

    template<bool head, bool... tail>
    struct mcase
    {
        constexpr mcase() = default;
        constexpr operator long long() const
        {
            return encode(tll(), 1+sizeof...(tail));
        }
        constexpr long long tll() const { return head | mcase<tail...>().tll() << 1; }
    };

    template<bool b>
    struct mcase<b>
    {
        constexpr mcase() = default;
        constexpr operator long long() const { return encode(tll(), 1); }
        constexpr long long tll() const { return b; }
    };
}

#define mswitch(head, ...) switch(mswitch::mswitch{head, __VA_ARGS__})
#define mcase(head, ...) case mswitch::mcase<head, __VA_ARGS__>()

#endif // MSWITCH_GUARD_H

Скомпилировать с g++ -std=c++14 -O2 -Wall -pedantic main.cpp

Как это работает

Объекты mswitch и mcase просто строят (во время компиляции, если это возможно, используя функции constexpr) биекцию между булевым списком и switch способным long long. Поскольку mcase заданы константы времени компиляции, все метки switch на самом деле являются непрерывной константой времени компиляции.

Ответ 3

Я бы сделал справочную таблицу для этого:

#include <iostream>
#include <string>

enum state {
  STATE_1,
  STATE_2,
  STATE_3,
  STATE_4,
  STATE_5,
  STATE_6,
  STATE_7,
  STATE_8,
};

state f(int a, bool b, const std::string& str) {
  static const state table[2][2][2] = {
    STATE_8, // 0, 0, 0
    STATE_7, // 0, 0, 1
    STATE_6, // 0, 1, 0
    STATE_5, // 0, 1, 1
    STATE_4, // 1, 0, 0
    STATE_3, // 1, 0, 1
    STATE_2, // 1, 1, 0
    STATE_1  // 1, 1, 1
  };
  return table[a < 0][b == false][str != "morning"];
}

int main() {
  std::cout << f(1, true, "morning") << std::endl;
}

Ответ 4

Я согласен, соответствие шаблонов очень хорошо подходит. К сожалению, встроенный switch очень ограничен в С++.

Существует довольно простая реализация булевого пакета компиляции.

#include <type_traits>

namespace detail
{
    constexpr std::size_t pack_bool(std::size_t result)
    {
        return result;
    }

    template<typename T, typename... Ts>
    constexpr std::size_t pack_bool(std::size_t result, T arg, Ts... args)
    {
        static_assert(std::is_same<bool, T>::value, "boolean expected");
        return pack_bool((result << 1) | arg, args...);
    }
}

template<typename T, typename... Ts>
constexpr std::size_t pack_bool(T arg, Ts... args)
{
    static_assert(std::is_same<bool, T>::value, "boolean expected");
    return detail::pack_bool(arg, args...);
}

Теперь вы можете использовать его в switch statement

#include <iostream>

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    switch (pack_bool(a >= 0, b == true, str == "morning"))
    {
        case pack_bool(false, false, false) : return STATE_1;
        case pack_bool(false, false, true)  : return STATE_2;
        case pack_bool(false, true,  false) : return STATE_3;
        case pack_bool(false, true,  true)  : return STATE_4;
        case pack_bool(true,  false, false) : return STATE_5;
        case pack_bool(true,  false, true)  : return STATE_6;
        case pack_bool(true,  true,  false) : return STATE_7;
        case pack_bool(true,  true,  true)  : return STATE_8;
    }
    return STATE_1;
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

Ответ 5

В качестве альтернативного ответа, использующего базовые функции С++, вы также можете рассмотреть использование тернарных операторов.

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    // How not to:
    if (a < 0)
        return b == true ? (str == "morning" ? STATE_4 : STATE_3) : (str == "morning" ? STATE_2 : STATE_1);
    else // a >= 0
        return b == true ? (str == "morning" ? STATE_8 : STATE_7) : (str == "morning" ? STATE_6 : STATE_5);
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

Результат довольно компактен при использовании только основных операторов.