提问人:FalcoGer 提问时间:11/3/2023 最后编辑:cpplearnerFalcoGer 更新时间:11/3/2023 访问量:95
如何检查模板类型名称是否与另一种类型相同但忽略模板参数
How to check if a template typename is the same as another type but ignoring template parameters
问:
我想制作一个提供测量单位的库,例如长度、质量等作为类型。我知道这样的库可能已经存在了,但我想学习如何自己做这样的事情。我从 中汲取了灵感,它使用了 ,所以我也使用了这种方法。但是我遇到了一个问题,即存在大量代码重复,特别是对于数学运算,例如.std::chrono::duration
std::ratio
+
我的方法是,我有一个基类,我从中派生出单独的测量值,例如 或 。这些单独的测量值之间的许多代码是相同的,例如 ,它采用两个测量值并将值相加,以校正它们之间的比率。例如,我可以将 5 米增加到 12 英尺。但是我不能把它放在基类中,因为如果我这样做了,我可以在长度上添加一个质量,这是无稽之谈,而这正是我首先试图纠正的事情。Measurement
Length
Mass
operator+
我的解决方案是将这些东西放在基类中,但添加一个 requires 子句,禁止 的类型与要添加到的对象的类型不同。
问题在于,我仍然希望将一个长度单位添加到另一个长度单位,但使用不同的模板参数。但是 c++ 将它们视为不同的类型,因此如果我使用 ,它会失败,阻止我将 5 米添加到 12 英尺,因为 (1 米)和 (1 英尺) 是不同的类型。other
std::ratio
std::same_as
Length<float, std::ratio<1>>
Length<float, std::ratio<100'000, 328'084>>
我怎样才能确保
using Kilogram = Measurements::Mass<float>;
using Meter = Measurements::Length<float>;
using Feet = Length<float, std::ratio<100'000, 328'084>>;
Meter m{5};
Kilogram kg{45};
// m + kg; // should error
Meter m2{Feet{22}}; // should work
Feet f1 = m2; // should work
// Feet f2 = 12; // should error
Meter m3 = m2 + ft; // should work
// Kilogram kg2 = m2; // should error
但是没有复制 Mass 和 Length 中的所有操作重载和构造函数?
下面是问题的示例
#include <iostream>
#include <ratio>
#include <type_traits>
#include <utility>
namespace Measurements
{
template <typename T>
concept IsArithmetic = requires { requires std::is_arithmetic_v<T>; };
template <typename R>
concept IsRatio = requires {
{
R::num
};
{
R::den
};
};
template <IsArithmetic Rep, IsRatio Factor = std::ratio<1, 1>>
class Measurement
{
private:
Rep m_value {};
public:
using REP = Rep;
using FACTOR = Factor;
static const auto NUM {Factor::num};
static const auto DEN {Factor::den};
explicit Measurement(Rep value) noexcept : m_value {value}
{ /* Empty */
}
// C-TORS (copy & move) and assignment (copy & move) overloads for
// Measurements<Rep, Factor> argument (defaulted with = default) and
// template <typename Rep2, Factor2> Measurements<Rep2, Factor2> argument
[[nodiscard]]
constexpr auto Value() const noexcept -> Rep
{
return m_value;
}
// Can't do this, it would allow adding kilograms to meters
/*
template <typename Rep2, typename Factor2>
constexpr auto operator+ (const Measurement<Rep2, Factor2>& other) const
{
using Adjustment = std::ratio_divide<Factor2, Factor>;
constexpr const double FACTOR = static_cast<double>(Adjustment::num) / static_cast<double>(Adjustment::den);
return Measurement<Rep, Factor> {
static_cast<Rep>(static_cast<double>(this->Value()) + (static_cast<double>(other.Value()) * FACTOR))
};
}
template <typename Rep2, typename Factor2>
constexpr auto operator+ (Measurement<Rep2, Factor2>&& other) const
{
using Adjustment = std::ratio_divide<Factor2, Factor>;
constexpr const double FACTOR = static_cast<double>(Adjustment::num) / static_cast<double>(Adjustment::den);
return Length<Rep, Factor> {
static_cast<Rep>(static_cast<double>(this->Value()) + (static_cast<double>(std::move(other).Value()) * FACTOR))
};
}
*/
};
template <typename Rep, typename Factor = std::ratio<1, 1>>
class Length final : public Measurement<Rep, Factor>
{
public:
Length() noexcept : Measurement<Rep, Factor>() {}
explicit Length(Rep value) noexcept : Measurement<Rep, Factor>(value) {};
Length(const Length<Rep, Factor>& other) noexcept : Measurement<Rep, Factor>(other) {};
Length(Length<Rep, Factor>&& other) noexcept : Measurement<Rep, Factor>(std::move(other)) {};
auto operator= (auto&& other) noexcept -> Length<Rep, Factor>&
{
Measurement<Rep, Factor>::operator= (std::forward(other));
return *this;
};
template <typename Rep2, typename Factor2>
explicit Length(const Length<Rep2, Factor2>& other) noexcept : Measurement<Rep, Factor>(other) {};
template <typename Rep2, typename Factor2>
explicit Length(Length<Rep2, Factor2>&& other) noexcept
: Measurement<Rep, Factor>(std::move(other)) {};
//NOLINTNEXTLINE (modernize-use-override)
virtual ~Length() final = default;
template <typename Rep2, typename Factor2>
constexpr auto operator+ (const Length<Rep2, Factor2>& other) const
{
using Adjustment = std::ratio_divide<Factor2, Factor>;
constexpr const double FACTOR = static_cast<double>(Adjustment::num) / static_cast<double>(Adjustment::den);
return Length<Rep, Factor> {
static_cast<Rep>(static_cast<double>(this->Value()) + (static_cast<double>(other.Value()) * FACTOR))
};
}
};
template <typename Rep, typename Factor = std::ratio<1, 1>>
class Mass final : public Measurements::Measurement<Rep, Factor>
{
// copy paste of Length
};
} // namespace Measurements
一个完整的例子可以在 Godbolt 上找到。
正如你所看到的,它非常冗长。如何保持类型安全,同时减少代码重复,尤其是运算符重载?
在最好的情况下,我只会从 Measurements 继承,不需要做任何事情,只需添加特殊的重载,例如将 Area 除以 Length 应该返回 Length。
编辑 1
我不知道我在做什么,我试了一下
template <template <IsArithmetic Rep1, IsRatio Factor1> typename Outer1, template <IsArithmetic Rep2, IsRatio Factor2> typename Outer2>
concept HasSameOuterType = requires {
requires std::same_as<Outer1<Rep1, Factor1>, Outer2<Rep1, Factor1>>;
};
但是,它说:
使用未声明的标识符“Rep1”(clang undeclared_var_use)
只是比较,也给出了一个错误:Outer1
Outer2
使用模板模板参数“Outer1”需要模板参数 (clang template_missing_args)
答:
这个概念本身并不难写。您可以在要求中使用带有 lambda 的类型推导。HasSameOuterType
template <typename T, typename U>
concept HasSameOuterType = requires (T t, U u) {
[]<template <typename R, typename F> typename Outer, typename Rep1, typename Factor1, typename Rep2, typename Factor2>
(Outer<Rep1, Factor1>&, Outer<Rep2, Factor2>&){}(t, u);
};
该概念可以进一步适应您的需求(例如,考虑引用和 cv 限定符,或要求在 lambda 中满足)。我在这里保持简单。Rep1
IsArithmetic
以下测试通过:
static_assert(Measurements::HasSameOuterType<Meter, Feet>);
static_assert(!Measurements::HasSameOuterType<Meter, Kilogramm>);
static_assert(Measurements::HasSameOuterType<std::pair<int, double>, std::pair<float, char>>);
static_assert(!Measurements::HasSameOuterType<std::pair<int, double>, std::tuple<int, double>>);
评论