Слишком много операторов if / else при преобразовании пользовательских данных в типы в C ++ - PullRequest
2 голосов
/ 19 апреля 2020

У меня есть шаблонный класс с 3 аргументами шаблона.

template <class T, class U, class Y>
class MyClass {};

Я хочу получать от пользователей информацию от аргументов CLI, что-то вроде ./cli float driver-x load

  • Первый аргумент может быть float или double
  • Второй аргумент - это имя драйвера: driver-x, driver-y, ...
  • Третий аргумент касается типа действия: load, unload, ...

Если я хочу создать новый экземпляр MyClass на основе пользовательских данных, я должен определить множество if/else операторов. Поскольку пользовательский ввод string, и я должен подготовить условие для них. Итак, это будет примерно так:

if (data_type == "float")
    if (driver == "driver-x")
        if (action == "load")
            MyClass<float, DriverX, Load> t;
            t......

Насколько я знаю, невозможно сохранить тип в переменной в C ++.

Итак, существует ли какой-либо способ для улучшить if/else заявления? Что-то вроде:

if (data_type == "float")
    //

if (driver == "driver-x")
   //

if (action == "load")
   //

MyClass<......> t;
t.....;

Или как-нибудь еще?

Я ищу способ улучшить эти if/else утверждения.

Ответы [ 4 ]

3 голосов
/ 19 апреля 2020

Вот мой дубль

template<typename T>
struct proxy { // or std::type_identity
    using type = T;
};
template<typename T, typename>
using type_const_t = T;

template<typename... Ts>
using choice_of = std::variant<proxy<Ts>...>;

template<typename... Ts>
struct chooser;
template<typename T>
struct chooser<T> {
    static choice_of<T> choose(std::string const &choice, std::string const &ignored) {
        return proxy<T>{}; // no "not found" checking in this version
    }
};
template<typename T, typename... Ts>
struct chooser<T, Ts...> {
    static choice_of<T, Ts...> choose(std::string const &choice, std::string const &head, type_const_t<std::string const&, Ts>... tail) {
        if(choice == head) return proxy<T>{};
        else return std::visit([](auto chosen) -> choice_of<T, Ts...> {
            return chosen;
        }, chooser<Ts...>::choose(choice, tail...));
    }
};

template<typename... Ts>
choice_of<Ts...> make_choice(std::string const &choice, type_const_t<std::string const&, Ts>... choices) {
    return chooser<Ts...>::choose(choice, choices...);
}

auto data_choice = make_choice<float, double>(data_type, "float", "double");
auto driver_choice = make_choice<DriverX, DriverY>(driver, "driver-x", "driver-y");
auto action_choice = make_choice<Load, Unload>(action, "load", "unload");
std::visit([](auto data_type_p, auto driver_p, auto action_p) {
    auto t =  MyClass<typename decltype(data_type_p)::type, typename decltype(driver_p)::type, typename decltype(action_p)::type>{};
    // do stuff with t
}, data_choice, driver_choice, action_choice);
2 голосов
/ 19 апреля 2020

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

Например, здесь я создаю кортеж, который содержит строки и типы, а затем проверяю переданную строку на соответствие всем их:

#include <string_view>
#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>

template<class T>
struct mapped_type {
    const std::string_view key;
    using type = T;

    explicit constexpr operator bool() const noexcept {
        return true;
    }
};

namespace detail {
    template<class K, class F, class M, std::size_t I>
    constexpr void lookup_impl(const K& key, F&& f, M&& m, std::integral_constant<std::size_t, I>) {
        using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
        if constexpr (I < std::tuple_size<tuple_t>::value) {
            const auto& mapping = std::get<I>(m);
            if (mapping.key == key) {
                std::forward<F>(f)(mapping);
                return;
            }
            lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});

        } else {
            std::forward<F>(f)(std::false_type{});
        }
    }
}

// Calls `f` with the first value from `m` that matches the key
// or `std::false_type{}` if no key matches.
template<class K, class F, class M>
constexpr void lookup(const K& key, F&& f, M&& m) {
    detail::lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, 0>{});
}

// This is our mapping for the first argument
inline constexpr auto data_type_map = std::make_tuple(
    mapped_type<float>{ "float" },
    mapped_type<double>{ "double" }
);

// Example usage
#include <iostream>

int main() {
    const char* s = "float";

    lookup(s, [](const auto& arg) {
        if constexpr (!arg) {
            std::cout << "Invalid type\n";
        } else {
            using type = typename std::remove_cv<typename std::remove_reference<decltype(arg)>::type>::type::type;
            std::cout << "Got type: " << typeid(type).name() << '\n';
        }
    }, data_type_map);
}

И затем вы можете вызвать это рекурсивно внутри лямбды.

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

#include <string_view>
#include <tuple>
#include <utility>
#include <type_traits>

template<class T>
struct mapped_type {
    const std::string_view key;
    using type = T;

    explicit constexpr operator bool() const noexcept {
        return true;
    }
};

namespace detail {
    template<class K, class F, class M, std::size_t I>
    constexpr void lookup_impl(F&& f, const K& key, M&& m, std::integral_constant<std::size_t, I>) {
        using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
        if constexpr (I < std::tuple_size<tuple_t>::value) {
            const auto& mapping = std::get<I>(m);
            if (mapping.key == key) {
                std::forward<F>(f)(mapping);
                return;
            }
            lookup_impl(std::forward<F>(f), key, std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});
        } else {
            std::forward<F>(f)(std::false_type{});
        }
    }

    template<class F, class K, class M, std::size_t I>
    constexpr void multilookup_impl(F&& f, const K& keys, M&& mappings, std::integral_constant<std::size_t, I>) {
        constexpr std::size_t size = std::tuple_size<typename std::remove_cv<typename std::remove_reference<K>::type>::type>::value;
        if constexpr (I >= size) {
            std::forward<F>(f)();
        } else {
            lookup_impl([&](const auto& current_lookup) {
                multilookup_impl(
                    [&](const auto&... args) { std::forward<F>(f)(current_lookup, args...); },
                    keys, mappings, std::integral_constant<std::size_t, I + 1>{}
                );
            }, std::get<I>(keys), std::get<I>(mappings), std::integral_constant<std::size_t, 0>{});
        }
    }
}

template<class F, class K, class M>
constexpr void lookup(F&& f, const K& keys, M&& mappings) {
    using map_tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
    using key_tuple_t = typename std::remove_cv<typename std::remove_reference<K>::type>::type;
    constexpr std::size_t size = std::tuple_size<key_tuple_t>::value;
    static_assert(size == std::tuple_size<map_tuple_t>::value, "Wrong number of keys for given number of maps");
    detail::multilookup_impl(std::forward<F>(f), keys, mappings, std::integral_constant<std::size_t, 0>{});
}

Что выглядит почти так же, но есть еще один уровень вызовов.

Он будет использоваться так:

#include <iostream>


inline constexpr auto data_type_map = std::make_tuple(
    mapped_type<float>{ "float" },
    mapped_type<double>{ "double" }
);

inline constexpr auto driver_type_map = std::make_tuple(
    mapped_type<DriverX>{ "driver-x" },
    mapped_type<DriverY>{ "driver-y" }
);

inline constexpr auto action_type_map = std::make_tuple(
    mapped_type<Load>{ "load" },
    mapped_type<Unload>{ "unload" }
);

int main() {
    const char* a = "float";
    const char* b = "driver-x";
    const char* c = "load";

    lookup([](const auto& data, const auto& driver, const auto& action) {
        if constexpr (!data) {
            std::cout << "Could not parse data!\n";
        } else if constexpr (!driver) {
            std::cout << "Could not parse driver!\n";
        } else if constexpr (!action) {
            std::cout << "Could not parse action!\n";
        } else {
            using data_type = typename std::remove_cv<typename std::remove_reference<decltype(data)>::type>::type::type;
            using driver_type = typename std::remove_cv<typename std::remove_reference<decltype(driver)>::type>::type::type;
            using action_type = typename std::remove_cv<typename std::remove_reference<decltype(action)>::type>::type::type;

            MyClass<data_type, driver_type, action_type> t;
            std::cout << "Constructed a " << typeid(decltype(t)).name() << '\n';
        }
    },
        std::array<const char*, 3>{ a, b, c },
        std::forward_as_tuple(data_type_map, driver_type_map, action_type_map)
    );
}
1 голос
/ 20 апреля 2020

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

Каждый возможный аргумент рассматривается только один раз в выделенной функции (не X раз Y раз Z если / еще).

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>
#include <string>
#include <stdexcept>

//----------------------------------------------------------------------------

struct DriverX { auto show() const { return "DriverX"; } };
struct DriverY { auto show() const { return "DriverY"; } };
struct Load    { auto show() const { return "Load";    } };
struct Unload  { auto show() const { return "UnLoad";  } };

template<typename RealType,
         typename DriverType,
         typename ActionType>
struct MyClass
{
  RealType real{};
  DriverType driver{};
  ActionType action{};
  auto show() const
  {
    return std::to_string(sizeof(real))+" bytes real, "+
           driver.show()+", "+action.show();
  }
};

//----------------------------------------------------------------------------

template<typename RealType,
         typename DriverType,
         typename DoEverythingFunction>
void
with_MyClass_3(const std::string &action,
               DoEverythingFunction fnct)
{
  if(action=="load")
  {
    return fnct(MyClass<RealType, DriverType, Load>{});
  }
  if(action=="unload")
  {
    return fnct(MyClass<RealType, DriverType, Unload>{});
  }
  throw std::runtime_error{"unexpected action: "+action};
}

template<typename RealType,
         typename DoEverythingFunction>
void
with_MyClass_2(const std::string &driver,
               const std::string &action,
               DoEverythingFunction fnct)
{
  if(driver=="driver-x")
  {
    return with_MyClass_3<RealType, DriverX>(action, fnct);
  }
  if(driver=="driver-y")
  {
    return with_MyClass_3<RealType, DriverY>(action, fnct);
  }
  throw std::runtime_error{"unexpected driver: "+driver};
}

template<typename DoEverythingFunction>
void
with_MyClass(const std::string &real,
             const std::string &driver,
             const std::string &action,
             DoEverythingFunction fnct)
{
  if(real=="float")
  {
    return with_MyClass_2<float>(driver, action, fnct);
  }
  if(real=="double")
  {
    return with_MyClass_2<double>(driver, action, fnct);
  }
  throw std::runtime_error{"unexpected real: "+real};
}

//----------------------------------------------------------------------------

int
main(int argc,
     char **argv)
{
  std::cout << "~~~~ hardcoded types ~~~~\n";
  const MyClass<float, DriverX, Load> mc1;
  std::cout << "mc1: " << mc1.show() << '\n';
  const MyClass<double, DriverY, Unload> mc2;
  std::cout << "mc2: " << mc2.show() << '\n';

  std::cout << "\n~~~~ many types ~~~~\n";
  for(const auto &real: {"float", "double", "int"})
  {
    for(const auto &driver: {"driver-x", "driver-y", "driver-z"})
    {
      for(const auto &action: {"load", "unload", "sleep"})
      {
        try
        {
          with_MyClass(real, driver, action,
            [&](const auto &mc)
            {
              std::cout << "working with: " << mc.show() << '\n';
            });
        }
        catch(const std::exception &e)
        {
          std::cerr << "!!! " << e.what() << " !!!\n";
        }
      }
    }
  }

  if(argc>3)
  {
    std::cout << "\n~~~~ from command line ~~~~\n";
    try
    {
      with_MyClass(argv[1], argv[2], argv[3],
        [&](const auto &mc)
        {
          std::cout << "working with: " << mc.show() << '\n';
        });
    }
    catch(const std::exception &e)
    {
      std::cerr << "!!! " << e.what() << " !!!\n";
    }
  }
  return 0;
}
1 голос
/ 19 апреля 2020

Я думаю, что вы ищете что-то вроде X-макроса:

    #define YOUR_TABLE \
    X(float, DriverX, "driver-x", Load) \
    X(int, DriverY, "driver-y", action2) \
    X(int, DriverY, "driver-y", action3)

    #define X(data_type, driver, driverName, action) if((0 == strcmp(#data_type,argv[1])) \
    && (0 == strcmp(driverName,argv[2])) && (0 == strcmp(#action,argv[3])))\
    { \
       MyClass<data_type, driver, action> t; \
       t.... \
    }
    YOUR_TABLE 
    #undef X
...