假设我有一个存储一些数据的类,
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
所以我不认为模板可能会有所帮助。
实际上,你要做的是写一个弱类型的类型。
这些类型支持脚本语言,如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);
}
也许你可以简单地编写一个方法,将每个使用类型的值转换为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();
}
途径
首先,让我们考虑对应枚举类Value::Type
的枚举器的原始类型的所有对组合。由于Value::Type
的所有枚举器都未显式初始化,因此第一个枚举器的值为0
,第二个枚举器的值为1
,依此类推。使用这些零起始整数,我们可以通过连续的零起始整数标记所有类型组合,如下所示:
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>
,这将成为你在帖子中写的int
和float
的比较。
然后我想提出以下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& lhs
和Value& 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
。
比较
总之,Variable::operator==
实现如下。这里make_comparator
在编译时生成struct comparator
,将所有类型组合传递给它的模板参数。 comparator
还在编译时创建指向函数check<T>
的指针数组。因此,两个Value
的比较将使用O(1)复杂度:
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);
}