使用不同的值类型推导地图的返回类型

时间:2014-02-03 14:30:24

标签: c++ boost c++11

我有以下代码

#include <boost\any.hpp>

#include <iostream>
#include <memory>
#include <map>
#include <string>

enum class tags { int_param, string_param };

class Parameters
{
public:
    template <typename T>
    void set(tags key, T value)
    {
        map_[key] = value;
    }

    template <typename T>
    T get(tags key)
    {
        return boost::any_cast<T>(map_[key]);
    }

private:
    std::map<tags, boost::any> map_;
};

int main()
{
    Parameters params;

    params.set(tags::int_param, 42);
    params.set(tags::string_param, "it works!");

    int int_par = params.get<int>(tags::int_param);
    std::string string_par = params.get<std::string>(tags::string_param);

    std::cout << "Channel: " << int_par << std::endl;
    std::cout << "Filename: " << string_par << std::endl;
}

这段代码有效,但我不喜欢提供模板类型,如果可能的话,我希望能够从地图中检索某些东西,而不必提供模板类型,即

int int_par = params.get(tags::int_param);

而不是

int int_par = params.get<int>(tags::int_param);

我理解通过使用boost :: any我们需要从任何类型转换为提供的类型,但有没有办法我们可以做到这一点而不必自己提供它,我们可以用其他方式推断它&gt; < / p>

3 个答案:

答案 0 :(得分:3)

没有办法从any中推断出任何东西,但可能有机会从声明中推断出某些内容。由于您的代码在any_cast失败时已经抛出,我认为这对您来说不是问题。

您可以扩展(由于缺少虚拟析构函数而真的是一个坏主意)boost::any或将其包装并提供模板化转换运算符。

#include <boost/any.hpp>
#include <iostream>

struct converting_any {
  converting_any(boost::any* a) : a(a) {}

  template<typename T>
  operator T() { return boost::any_cast<T>(*a); } // dereference to get the throwing version
private:
  boost::any* a; // wrapped any
};

int main()
{
  int i = 3; std::string j = "asddf";
  boost::any a = i;

  int i2 = converting_any(&a);
  try {
    std::string j2 = converting_any(&a);
  } catch(...) {
    std::cout << "Failure." << std::endl;
  }

  a = j;
  std::string j3 = converting_any(&a);

  return 0;
}

答案 1 :(得分:3)

如果您将标签从枚举值更改为结构,您可以实现所需的行为,甚至是其他良好的功能,例如类型安全性以及我们对any_cast的使用不会抛出。

通过使用结构,我们可以定义简单的特征来获取每个标签的值类型,例如

enum class tag_id
{
  int_param; // Id which represents int_param tag on std::map
}

struct int_param_t
{
  using value_type = int;
  static const tag_id id;
};
const tag_id int_param_t::id = tag_id::int_param;

现在我们可以使用这个特性来实现您想要的语法

template< typename Tag >
auto get( Tag ) -> typename Tag::valye_type
{
  return boost::any_cast<typename Tag::valye_type>(map_[Tag::id]);
}

// ...

int         a = parameters.get( int_param_t{} ); // Ok, returns int.
std::string b = parameters.get( int_param_t{} ); // Error, int is not convertible to std::string.

作为奖励,我们可以使用此特征来确保只有可转换为正确类型的类型与set函数一起使用

template< typename Tag >
void set( Tag, typename Tag::value_type value )
{
  map_[Tag::id] = value;
}

// ...

parameter.set( int_param_t{},        0 ); // Ok, 0 is convertible to int.
parameter.set( int_param_t{}, "string" ); // Error, const char[] is not convertible to int.

为了使它更漂亮,我还会定义一些别名和常量,例如

// Helper alias to avoid writing typename everywhere.
template< typename T >
using param_type = typename T::value_type;

// Used to avoid having to create tag objects every function call.
constexpr int_param_t int_param{};

这是最终产品

#include <boost/any.hpp>

#include <iostream>
#include <memory>
#include <map>
#include <string>

namespace tags
{
    enum class tag_id
    {
        int_param,
        string_param
    };

    struct int_param_t
    {
        using value_type = int;
        static const tag_id id;
    };
    const tag_id int_param_t::id = tag_id::int_param;

    constexpr int_param_t int_param{};

    struct string_param_t
    {
        using value_type = std::string;
        static const tag_id id;
    };
    const tag_id string_param_t::id = tag_id::string_param;

    constexpr string_param_t string_param{};
}

// Helper alias to avoid writing typename everywhere.
template< typename T >
using param_type = typename T::value_type;

class Parameters
{
public:
    template <typename Tag>
    void set(Tag, param_type< Tag > value)
    {
        map_[Tag::id] = value;
    }

    template <typename Tag>
    auto get(Tag) -> param_type< Tag >
    {
        return boost::any_cast< param_type< Tag > >(map_[Tag::id]);
    }

private:
    std::map<tags::tag_id, boost::any> map_;
};

int main()
{
    Parameters params;

    params.set(tags::int_param, 42);
    params.set(tags::string_param, "it works!");

    int int_par = params.get(tags::int_param);
    std::string string_par = params.get(tags::string_param);

    std::cout << "Channel: " << int_par << std::endl;
    std::cout << "Filename: " << string_par << std::endl;
}

答案 2 :(得分:2)

你可以返回一个引用包装器,它将转换推迟到需要的时候:

struct converter {
    converter(boost::any & any) : any(any) {}

    template <typename T>
    operator T() {return boost::any_cast<T>(any);}

    boost::any & any;
};

converter get(tags key)
{
    return map_[key];
}

模板参数可以从转换为的类型推导出来,因此您的int示例将在没有显式模板参数的情况下工作。

请注意,与您的版本一样,如果模板参数与变体的类型不匹配,则会失败,如您在尝试将const char *转换为std::string时的示例所示。