Оператор приведения шаблона и boost :: any или std :: any - PullRequest
0 голосов
/ 16 апреля 2020

Я написал обобщенный c класс, чтобы обеспечить простой JSON инициацию для любого класса. Это работало как талисман, пока я не хочу применить его к классу, содержащему перечисление.

мой базовый класс сначала проанализирует JSON, найдите подобъект JSON, который имеет тот же name, чем производный класс (я использую CRTP ...), и создайте std::map<std::string, boost::any> _settings;, где ключ - это имя поля и boost :: any, содержит строку, int, double и четный массив чисел.

Мой производный класс просто должен реализовать функцию Update, как в следующем примере:

class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
{
public:
  static const std::string class_name;

  testJsonUpdate(const std::string& settings) {
    _test_int = 0;
    _test_vector = { 0.0f };

    ParseAndUpdate(settings);
  }
  ~testJsonUpdate() {}

  int Update()
  {
    UPDATE_VALUE(testJsonUpdate, _test_int);
    UPDATE_VALUE(testJsonUpdate, _test_vector);

    return 0;
  }

public:
  uint32_t _test_int;
  std::vector<float> _test_vector;
};
const std::string testJsonUpdate::class_name = "testJsonUpdate";

UPDATE_VALUE(testJsonUpdate, _test_int); - это MACRO, и его расширение использует следующий код. Вопрос в том, что я могу сделать, если в моем производном классе есть член emun. В этом случае значение boost :: any является целым числом, и пока вызов explicit operator T() с T = "MYCLASS :: enum type" вызывает исключение из-за boost :: any_cast от int до мой тип enum!

есть ли способ написать оператор приведения, который повышает :: любое использование приведения для решения этой проблемы?

template<typename CALLER>
struct Value
{
std::string _key;
boost::any _value;

template<typename T>
explicit operator T() const
{
  try
  {
    return boost::any_cast<T>(_value); // <== where it trigger exception
  }
  catch (...)
  {
    throw std::logic_error(CALLER::class_name + ": config string parsing issue");
  }
}

template<typename T>
static Value<T> RetreiveValue(const std::map<std::string, boost::any> & settings, const   std::string & key)
{
  return{ key, settings.find(key)->second };
 } // RetreiveValue

#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
   member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}

Полный пример, демонстрирующий проблему, протестирован на https://www.onlinegdb.com/online_c++_compiler с C ++ 17 (для поддержки std :: any)

#include <iostream>
using namespace std;

#include <string>
#include <map>
#include <vector>
#include <memory>
#include <any>


  template<typename CALLER>
  struct Value
  {
    std::string _key;
    std::any _value;

    template<typename T>
    explicit operator T() const
    {
      try
      {
        return std::any_cast<T>(_value);
      }
      catch (...)
      {
        //throw std::logic_error(CALLER::class_name + ": config string parsing issue");
        throw;
      }
    }
  }; // Value

  template<typename T>
  static Value<T> RetreiveValue(const std::map<std::string, std::any> & settings, const std::string & key)
  {
    return{ key, settings.find(key)->second };
  } // RetreiveValue

    // ----------------------------------------------------------------------------
  template <typename CALLER, typename T> const std::string buildPrefix(const T &elt)
  {
    throw std::logic_error(CALLER::class_name + ": config string parsing issue");
  } // buildName

  template <typename CALLER> const std::string buildPrefix(const bool &elt) { return "b"; }
  template <typename CALLER> const std::string buildPrefix(const std::string &elt) { return "s"; }
  template <typename CALLER> const std::string buildPrefix(const int &elt) { return "i"; }


#define ADD_M_PREFIX(member) m ## member

#define BUILD_KEY(caller, member) buildPrefix<caller>(member) + #member;
#define BUILD_KEY_WITH_M_PREFIX(caller, member) buildPrefix<caller>( ADD_M_PREFIX(member) ) + #member;
#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
  member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}

#define UPDATE_VALUE_WITH_M_PREFIX(caller, member) \
key = BUILD_KEY_WITH_M_PREFIX(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
  ADD_M_PREFIX(member) = (decltype(ADD_M_PREFIX(member))) RetreiveValue<caller>(_settings, key); \
}

  template<typename T>
  class JsonSettingsCustomizer {
  public:
    JsonSettingsCustomizer() : _label(T::class_name) { }
    virtual ~JsonSettingsCustomizer() {}

    int ParseAndUpdate(const std::string& settings) {
      //JSON Parsing to map

      //fake data to test
      _settings["i_integer"] = (int)(1);
      _settings["s_msg"] = (std::string)("hello world");
      _settings["i_enum_value"] = (int)(1);

      T& derived = static_cast<T&>(*this);
      auto ret = derived.Update();

      return ret;
    }

  protected:
    std::map<std::string, std::any>  _settings;
    std::string key;
    std::string _label;

  };
  /************************************************************************************************/
  //    END TOOLING
  /************************************************************************************************/

  typedef enum : int  {
     enum_one = 1,
     enum_two = 2
  } ENUM_TYPE;
//extention for the new ENUM_TYPE
template<typename CALLER> const std::string buildPrefix(const ENUM_TYPE& elt) { return "i"; }

class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
  {
    public:
      static const std::string class_name;

      testJsonUpdate(const std::string& settings)  {
            ParseAndUpdate(settings);
      }
      ~testJsonUpdate() {}

      int Update()
      {
        UPDATE_VALUE(testJsonUpdate, _integer);
        UPDATE_VALUE(testJsonUpdate, _msg);
        //UPDATE_VALUE(testJsonUpdate, _enum_value); // uncomment to break on the bad cast exception

        return 0;
      }

    public:
      int _integer;
      std::string _msg;
      ENUM_TYPE _enum_value;
  };
  const std::string testJsonUpdate::class_name = "testJsonUpdate";

int main() {
    // your code goes here
    testJsonUpdate o(".... JSON .... ");
    std::cout << o._integer << std::endl;
    std::cout << o._msg << std::endl;
    return 0;
}

1 Ответ

2 голосов
/ 16 апреля 2020

Я не знаком с бустом, поэтому буду использовать стандартные эквивалентные конструкции. Первый вопрос: вы действительно хотите any или variant<string, int, ...> лучший выбор?

В любом случае, если вы просто хотите обернуть актерский состав, вы можете сделать это легко:

template <typename T>
T json_cast(std::any const& val) 
{
    if constexpr (std::is_enum_v<T>)
    {
        return static_cast<T>(std::any_cast<int>(val));
    }
    else
    {
        return std::any_cast<T>(val);
    }
}

Это требует, чтобы any действительно содержал int. Вы также можете попробовать std::underlying_type_t<T> вместо int, но у перечислений есть некоторые причуды с их базовыми типами, так что приведение может фактически завершиться неудачей, если any содержит int.

C ++ 14 версия:

template <typename T>
T json_cast(std::any const& val) 
{    
    using cast_t = typename std::conditional<std::is_enum<T>::value, int, T>::type;
    return static_cast<T>(std::any_cast<cast_t>(val)); 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...