如何比较给定指针和类型的两个值

问题描述 投票:6回答:3

假设我有一个存储一些数据的类,

class Value {
public:
    enum class Type {
        int_type,
        float_type,
        double_type,
        bool_type
    };

    friend bool operator==(const Value& lhs, const Value& rhs) {
        //  how to make this function clean and concise?
    }

private:
    void* ptr;
    Type type;
};

ptr指向潜在价值,type指示如何施放ptr

为了比较Value对象,我绝对可以列出所有可能的类型组合,但代码将很难维护。喜欢:

if (lhs.type == Type::int_type && rhs.type == Type::float_type) {
    return *static_cast<int*>(lhs.ptr) == *static_cast<float*>(rhs.type);
}

知道如何降低复杂性吗?

更新:

我希望这个类是动态类型,这意味着我可以执行以下操作:

Value v("abc");       // v is now a string
v = 123;              // v is now an int
bool b = (v == 123.0); // b is true

所以我不认为模板可能会有所帮助。

c++
3个回答
3
投票

实际上,你要做的是写一个弱类型的类型。

这些类型支持脚本语言,如python和javascript。

写这样的类型比你最初想象的要复杂得多。

但是,一旦定义了转换矩阵,就会变得更容易(例如,将字符串与bool进行比较的规则是什么?)

这是一个开始,使用std::variant

#include <variant>
#include <string>
#include <type_traits>
#include <algorithm>
#include <cassert>

//
// Step 1 - define your conversion rules
//          this is not as trivial as you might think
//

template<class To>
struct convert;

template<>
struct convert<int>
{
    template<class From>
    auto operator()(From const& from) const -> int
    {
        return int(from);
    }
    auto operator()(std::string const& from) const -> int
    {
        return std::atoi(from.c_str());
    }
};
template<>
struct convert<double>
{
    template<class From>
    auto operator()(From const& from) const -> double
    {
        return double(from);
    }
    auto operator()(std::string const& from) const -> double
    {
        return std::atof(from.c_str());
    }
};
template<>
struct convert<bool>
{
    template<class From>
    auto operator()(From const& from) const -> bool
    {
        return bool(from);
    }
    auto operator()(std::string  from) const -> bool
    {
        auto lcase = [](auto ch) { return std::tolower(ch); };
        std::transform(from.begin(), from.end(), from.begin(), lcase);
        if (from == "true" || from == "yes" || std::atoi(from.c_str()))
            return true;
        else
            return false;
    }
};
template<>
struct convert<std::string>
{
    template<class From>
    auto operator()(From const& from) const -> std::string
    {
        return std::to_string(from);
    }

    auto operator()(bool const& from) const -> std::string
    {
        auto result = std::string();
        if (from)
            result.assign("true");
        else
            result.assign("false");
        return result;
    }

    auto operator()(std::string const& from) const -> std::string const&
    {
        return from;
    }
};

//
// Step 2 - use a std::variant
//

struct Value 
{
    explicit Value(int arg): store_(arg) {}
    explicit Value(double arg): store_(arg) {}
    explicit Value(bool arg): store_(arg) {}
    explicit Value(std::string arg): store_(std::move(arg)) {}
    explicit Value(const char* arg): store_(std::string(arg)) {}

    friend bool operator==(const Value& lhs, const Value& rhs) 
    {
        auto compare = [](auto &&l , auto&& r) 
        { 
            using l_type = std::decay_t<decltype(l)>;
            auto conv = convert<l_type>();
            return l == conv(r);
        };
        return std::visit(compare, lhs.store_, rhs.store_);
    }

private:
    using storage_type = std::variant<int, double, bool, std::string>;

private:
    storage_type store_;
};

int main()
{
    auto vt = Value(true);
    auto vst = Value("true");
    assert(vt == vst);
}

1
投票

也许你可以简单地编写一个方法,将每个使用类型的值转换为double(一个带有单个普通开关的方法),然后在比较运算符中比较两个双精度数?像这样:

private:
    double ToDouble() const
    {
        switch (type)
        {
        case Type::int_type: return *static_cast<int*>(ptr);
        case Type::float_type: return *static_cast<float*>(ptr);
        case Type::double_type: return *static_cast<double*>(ptr);
        case Type::bool_type: return *static_cast<bool*>(ptr) ? 1.0 : 0.0;
        }
    }
public:
    friend bool operator==(const Value& lhs, const Value& rhs)
    {
        return lhs.ToDouble() == rhs.ToDouble();
    }

1
投票

途径

首先,让我们考虑对应枚举类Value::Type的枚举器的原始类型的所有对组合。由于Value::Type的所有枚举器都未显式初始化,因此第一个枚举器的值为0,第二个枚举器的值为1,依此类推。使用这些零起始整数,我们可以通过连续的零起始整数标记所有类型组合,如下所示:

Live DEMO

std::pair<int , int   >   -->  4*int_type  + int_type    = 4*0+0 = 0
std::pair<int , float >   -->  4*int_type  + float_type  = 4*0+1 = 1
std::pair<int , double>   -->  4*int_type  + double_type = 4*0+2 = 2
...                            ...
std::pair<bool, bool  >   -->  4*bool_type + bool_type   = 4*3+3 = 15

接下来,我们介绍以下静态成员函数模板Value::check,它为每种类型组合提供了一般比较:

template<class T>
static bool check(const Value& lhs, const Value& rhs)
{
    return *static_cast<typename T::first_type*>(lhs.ptr)
            == *static_cast<typename T::second_type*>(rhs.ptr);
}

例如,如果T = std::pair<int, float>,这将成为你在帖子中写的intfloat的比较。

然后我想提出以下O(1)方法。在编译时,我们构造以下数组存储指向函数的指针,arr[i]是指向check<T>的指针,其中T是上述类型组合的i-th类型:

using comp_f = bool(*)(const Value& lhs, const Value& rhs);
comp_f arr[16] = { &check<std::pair<int, int>>, &check<std::pair<int, float>>, ... };

在运行时,给定Value& lhsValue& rhs,我们计算相应的索引并调用适当实例化的函数check<T>如下。这个过程可以用O(1)复杂度完成:

std::size_t idx = 4*static_cast<std::size_t>(lhs.type) 
                  + static_cast<std::size_t>(rhs.type); // 0 ~ 15.

return arr[idx](lhs, rhs);

组合学

现在我们的问题是如何简单地构造所有类型组合。我已经回答了almost same question这个问题。在当前情况下,应用此方法,可以通过以下struct Combinations生成所有可能的组合(并且也可以使用max66的方法)。请注意,这里我使用std::index_sequence,因此这适用于C ++ 14及更高版本。但是有various way在C ++ 11中实现std::index_sequence

template<std::size_t I, class Tuple>
using pairing = std::pair<
                typename std::tuple_element<I/std::tuple_size<Tuple>::value, Tuple>::type,
                typename std::tuple_element<I%std::tuple_size<Tuple>::value, Tuple>::type>;

template <class T, class Is>
struct make_combinations;

template <class Tuple, std::size_t... Is>
struct make_combinations<Tuple, std::index_sequence<Is...>>
{
    using pairs = std::tuple<pairing<Is, Tuple>...>;
};

template<class ...Args>
struct Combinations
{
    using types_tuple = typename make_combinations
                         <std::tuple<Args...>,
                          std::make_index_sequence<(sizeof...(Args))*(sizeof...(Args))>
                         >::pairs;
};

使用这个Combinations,我们可以生成所有类型组合的元组作为Combinations<int, float, double, bool>::types_tuple

Live DEMO


比较

总之,Variable::operator==实现如下。这里make_comparator在编译时生成struct comparator,将所有类型组合传递给它的模板参数。 comparator还在编译时创建指向函数check<T>的指针数组。因此,两个Value的比较将使用O(1)复杂度:

Live DEMO

template<std::size_t N, class T>
struct comparator {};

template<std::size_t N, class... Combs>
struct comparator<N, std::tuple<Combs...>>
{
    using comp_f = bool(*)(const Value& lhs, const Value& rhs);
    const comp_f arr[sizeof...(Combs)];

public:
    constexpr comparator() : arr{ &check<Combs>... }
    {}

    bool operator()(const Value& lhs, const Value& rhs) const
    {
        const std::size_t idx = N*static_cast<std::size_t>(lhs.type)
                                + static_cast<std::size_t>(rhs.type);

        return arr[idx](lhs, rhs);
    }
};

template<class... Ts>
static constexpr auto make_comparator()
{     
    return comparator<sizeof...(Ts), typename Combinations<Ts...>::types_tuple>();
}

friend bool operator==(const Value& lhs, const Value& rhs)
{
    constexpr auto comp = make_comparator<int, float, double, bool>();
    return comp(lhs, rhs);
}
© www.soinside.com 2019 - 2024. All rights reserved.