还记得那种类型的擦除设备吗
std::any
和
std::function
仅公开接口
他们只承诺:
有吗
封装了复制能力,但仅此而已,因此您无法
比较等式/哈希a
有吗
对象使用额外的
std::函数
只是为了存储
operator<
如果很麻烦(基本上每种类型都使用两个vtable),那么你就更好了
使用手卷式擦除。
此外,根据您的需求推断,您
不得不
特例
const char*
或
const char(&)[N]
参数,因为您希望将它们存储为
std::string
,以及它们的比较运算符。这
还解决了“存储
std::tuple
参考成员
有吗
“问题。(有关更多讨论,请参见编辑注释#2。)
在某些地方,你的godbolt链接中的代码是不正确的,尤其是当你通过时
T的构造函数构造
有吗
(错过了
std::in_place_type<T>
在前线,
也就是说)。
为了方便起见,下面的实现使用C++20,但它可以在
经过一些修改的旧标准。
编辑#1
:修复了未初始化的初始哈希值,这对我来说真的是一个noob错误。
编辑#2
:是的,特例的诀窍
常量字符*
不太好,而且它阻止了
常量字符*
不工作。你可以把它改写成“只需衰减每个参数,不需要对每个参数采取任何特殊措施”
常量字符*
或
常量字符(&)[N]
,这将适用于所有的c'Tor。但这也只适用于传入字符串文本的情况,否则可能会在哈希映射中存储一个悬空指针。如果您指定了真正希望通过其传递引用的每个位置,这种方法可能是可行的
std::string
(例如,通过使用UDL-like
"hello"s
或者显式地构造
std::string
).
AFEK不能获得Ccor的参数类型,因为C++明确禁止使用Ccor的地址,如果不能形成指向成员函数的指针,就不能在其上做模板技巧。此外,超负荷解决可能是实现这一目标的另一个障碍。
编辑#3
:我没有注意到会有不可复制的对象要缓存。在这种情况下,
有吗
没有用,因为它只能存储可复制的对象。使用类似类型的擦除技术,也可以存储不可复制的对象。我的实现只是使用
std::unique_ptr
存储擦除的键和值,强制将它们存储在堆上。这个简单的方法甚至支持不可复制
和
不可移动的字体。如果需要SBO,则必须使用更复杂的方法来存储类型擦除的对象。
#include <iostream>
#include <unordered_map>
#include <type_traits>
// Algorithm taken from boost
template <typename T>
void hash_combine(std::size_t& seed, const T& value)
{
static constexpr std::size_t golden_ratio = []
{
if constexpr (sizeof(std::size_t) == 4)
return 0x9e3779b9u;
else if constexpr (sizeof(std::size_t) == 8)
return 0x9e3779b97f4a7c15ull;
}();
seed ^= std::hash<T>{}(value) + golden_ratio +
std::rotl(seed, 6) + std::rotr(seed, 2);
}
class Factory
{
public:
template <typename T, typename... Args>
const T& get(Args&&... args)
{
Key key = construct_key<T, Args...>(static_cast<Args&&>(args)...);
if (const auto iter = cache_.find(key); iter != cache_.end())
return static_cast<ValueImpl<T>&>(*iter->second).value;
Value value = key->construct();
const auto [iter, emplaced] = cache_.emplace(
std::piecewise_construct,
// Move the key, or it would be forwarded as an lvalue reference in the tuple
std::forward_as_tuple(std::move(key)),
// Also the value, remember that this tuple constructs a std::any, not a T
std::forward_as_tuple(std::move(value))
);
return static_cast<ValueImpl<T>&>(*iter->second).value;
}
private:
struct ValueModel
{
virtual ~ValueModel() noexcept = default;
};
template <typename T>
struct ValueImpl final : ValueModel
{
T value;
template <typename... Args>
explicit ValueImpl(Args&&... args): value(static_cast<Args&&>(args)...) {}
};
using Value = std::unique_ptr<ValueModel>;
struct KeyModel
{
virtual ~KeyModel() noexcept = default;
virtual std::size_t hash() const = 0;
virtual bool equal(const KeyModel& other) const = 0;
virtual Value construct() const = 0;
};
template <typename T, typename... Args>
class KeyImpl final : public KeyModel
{
public:
template <typename... Ts>
explicit KeyImpl(Ts&&... args): args_(static_cast<Ts&&>(args)...) {}
// Use hash_combine to get a hash
std::size_t hash() const override
{
std::size_t seed{};
std::apply([&](auto&&... args)
{
(hash_combine(seed, args), ...);
}, args_);
return seed;
}
bool equal(const KeyModel& other) const override
{
const auto* ptr = dynamic_cast<const KeyImpl*>(&other);
if (!ptr) return false; // object types or parameter types don't match
return args_ == ptr->args_;
}
Value construct() const override
{
return std::apply([](const Args&... args)
{
return std::make_unique<ValueImpl<T>>(args...);
}, args_);
}
private:
std::tuple<Args...> args_;
};
using Key = std::unique_ptr<KeyModel>;
using Hasher = decltype([](const Key& key) { return key->hash(); });
using KeyEqual = decltype([](const Key& lhs, const Key& rhs) { return lhs->equal(*rhs); });
std::unordered_map<Key, Value, Hasher, KeyEqual> cache_;
template <typename T, typename... Args>
static Key construct_key(Args&&... args)
{
constexpr auto decay_or_string = []<typename U>(U&& arg)
{
// convert to std::string if U decays to const char*
if constexpr (std::is_same_v<std::decay_t<U>, const char*>)
return std::string(arg);
// Or just decay the parameter otherwise
else
return std::decay_t<U>(arg);
};
using KeyImplType = KeyImpl<T, decltype(decay_or_string(static_cast<Args&&>(args)))...>;
return std::make_unique<KeyImplType>(decay_or_string(static_cast<Args&&>(args))...);
}
};
struct IntRes
{
int id;
explicit IntRes(const int id): id(id) {}
};
struct StringRes
{
std::string id;
explicit StringRes(std::string id): id(std::move(id)) {}
};
int main()
{
Factory factory;
std::cout << factory.get<IntRes>(42).id << std::endl;
std::cout << factory.get<StringRes>("hello").id << std::endl;
}