使用 C++ 概念在不同模板类型上重载运算符
Overload operators on different templated types with C++ concepts
我正在尝试为不同的模板类型提供算术运算符 +-*/
(以及就地 +=
等)的超出 class 的定义。我读到 C++20 概念是一个很好的方法,因为可以限制 input/output 类型只提供一个模板化定义,尽管我找不到太多这样的例子...
我正在使用类型安全的向量作为基础 class:
// vect.cpp
template<size_t n, typename T>
struct Vect {
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
}
我有一个像这样的张量的派生 class:
// tensor.cpp
template <typename shape, typename T>
struct Tensor : public Vect<shape::size, T> {
// ... same initiliazer and [](size_t i)
}
并且我还将为只读 views/slices 定义一个派生的 class,覆盖 operator []
以跨越大步。我想在每个 class 中对 fmap
和 fold
方法进行硬编码,并尽可能避免复制样板代码。
由于模板参数不同,我一开始在想出适合 Vect<n,T>
类 class 的概念时遇到了一些麻烦,但下面的似乎可行:
// main.cpp
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
int main () {
size_t n = 10;
typedef double T;
Vect<n,T> u ([&] (size_t i) {return static_cast<T>(i) / static_cast<T>(n);});
log("u + u", u);
return 0;
}
Error: template deduction/substitution failed, could not deduce template parameter 'n'
尝试 2:
基于 this question,我认为 class 的定义必须更冗长一点,所以我在 vect.cpp
.
中添加了几行
这似乎是人为的,因为它需要 (3 * N_operators) 类型签名定义,避免代码重复是激发这个问题的原因。另外我真的不明白 friend
关键字在这里做什么。
// vect.cpp
template<size_t n, typename T>
struct Vect;
template<size_t n, typename T>
Vect<n, T> operator + (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
template<size_t n, typename T>
struct Vect {
...
friend Vect operator +<n, T> (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
...
}
Error: undefined reference to Vect<10, double> operator+(Vect<10, double> const&, Vect<10, double> const&)' ... ld returned 1 exit status
我猜编译器抱怨在 main.cpp
而不是 vect.cpp
中定义了实现?
问题:执行此操作的正确 C++ 方法是什么?有什么方法可以让编译器满意吗?有头文件吗?
我真的在这里寻找 DRY 答案,因为我知道代码可以使用大量的复制粘贴:)
谢谢!
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
这里,你得有办法推导出n
和T
。您的 V
没有提供; C++ 模板参数推导不会反转 non-trivial 模板构造(因为通常这样做是 Halt-hard,它有一个规则,这使得它成为 non-deduced)。
查看 body,您 不需要 n
或 T
。
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
这是你想要的签名。
下一步是让它发挥作用。
现在,您现有的概念有问题:
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
这个概念正在查看 V
的 实现 ,看看它是否源自 Vect
。
假设有人用相同的界面用 Vect2
重写了 Vect
。它不应该也是一个向量吗?
查看 Vect 的实现:
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
它可以从 std::function<T(size_t)>
构造并具有 [size_t]->T
运算符。
template<class T, class Indexer=std::size_t>
using IndexResult = decltype( std::declval<T>()[std::declval<Indexer>()] );
这是一个特征,说明 v[0]
的类型结果是什么。
template<class V>
concept Vector = requires (V const& v, IndexResult<V const&>(*pf)(std::size_t)) {
typename IndexResult<V const&>;
{ V( pf ) };
{ v.size() } -> std::convertible_to<std::size_t>;
};
好了,Vector
基于 duck-type 的概念。我添加了一个 .size()
方法要求。
然后我们在所有 Vector
上写一些操作:
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
template<Vector V>
std::ostream& operator<<(std::ostream& os, V const& v)
{
for (std::size_t i = 0; i < v.size(); ++i)
os << v[i] << ',';
return os;
}
修复你的基地Vect
一点点:
template<std::size_t n, typename T>
struct Vect {
Vect(std::function<T(std::size_t)> f) {
for (std::size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (std::size_t i) const { // << here
return values[i];
}
constexpr std::size_t size() const { return n; } // << and here
};
然后这些测试通过:
constexpr std::size_t n = 10;
typedef double T;
MyNS::Vect<n,T> u ([&] (size_t i) {return (T)i / (T)n;});
std::cout << "u + u" << (u+u) << "\n";
(我正确地使用了名称空间,因为当我不这样做时我会感到恶心)。
请注意,operator+
是通过 ADL 找到的,因为它与 Vect
一样位于 MyNS
中。对于 MyNS
之外的类型,您必须将其 using MyNS::operator+
放入当前范围。这是故意的,几乎是不可避免的。
(如果您继承自 MyNS
中的某些内容,它也会被找到)。
...
TL;DR
概念通常应该是duck typed,这取决于你可以用类型做什么,而不是类型是如何实现的。该代码似乎并不关心您是否继承自特定类型或模板,它只是想使用一些方法;所以测试 that.
这也避免了尝试将模板参数推导到 Vect
class;我们改为从界面中提取它。
我正在尝试为不同的模板类型提供算术运算符 +-*/
(以及就地 +=
等)的超出 class 的定义。我读到 C++20 概念是一个很好的方法,因为可以限制 input/output 类型只提供一个模板化定义,尽管我找不到太多这样的例子...
我正在使用类型安全的向量作为基础 class:
// vect.cpp
template<size_t n, typename T>
struct Vect {
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
}
我有一个像这样的张量的派生 class:
// tensor.cpp
template <typename shape, typename T>
struct Tensor : public Vect<shape::size, T> {
// ... same initiliazer and [](size_t i)
}
并且我还将为只读 views/slices 定义一个派生的 class,覆盖 operator []
以跨越大步。我想在每个 class 中对 fmap
和 fold
方法进行硬编码,并尽可能避免复制样板代码。
由于模板参数不同,我一开始在想出适合 Vect<n,T>
类 class 的概念时遇到了一些麻烦,但下面的似乎可行:
// main.cpp
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
int main () {
size_t n = 10;
typedef double T;
Vect<n,T> u ([&] (size_t i) {return static_cast<T>(i) / static_cast<T>(n);});
log("u + u", u);
return 0;
}
Error: template deduction/substitution failed, could not deduce template parameter 'n'
尝试 2:
基于 this question,我认为 class 的定义必须更冗长一点,所以我在 vect.cpp
.
这似乎是人为的,因为它需要 (3 * N_operators) 类型签名定义,避免代码重复是激发这个问题的原因。另外我真的不明白 friend
关键字在这里做什么。
// vect.cpp
template<size_t n, typename T>
struct Vect;
template<size_t n, typename T>
Vect<n, T> operator + (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
template<size_t n, typename T>
struct Vect {
...
friend Vect operator +<n, T> (const Vect<n, T>& lhs, const Vect<n, T>& rhs);
...
}
Error: undefined reference to Vect<10, double> operator+(Vect<10, double> const&, Vect<10, double> const&)' ... ld returned 1 exit status
我猜编译器抱怨在 main.cpp
而不是 vect.cpp
中定义了实现?
问题:执行此操作的正确 C++ 方法是什么?有什么方法可以让编译器满意吗?有头文件吗?
我真的在这里寻找 DRY 答案,因为我知道代码可以使用大量的复制粘贴:)
谢谢!
template<int n, typename T, Vector<n, T> V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
这里,你得有办法推导出n
和T
。您的 V
没有提供; C++ 模板参数推导不会反转 non-trivial 模板构造(因为通常这样做是 Halt-hard,它有一个规则,这使得它成为 non-deduced)。
查看 body,您 不需要 n
或 T
。
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
这是你想要的签名。
下一步是让它发挥作用。
现在,您现有的概念有问题:
template<typename V, int n, typename T>
concept Vector = derived_from<V, Vect<n, T>>
这个概念正在查看 V
的 实现 ,看看它是否源自 Vect
。
假设有人用相同的界面用 Vect2
重写了 Vect
。它不应该也是一个向量吗?
查看 Vect 的实现:
Vect(function<T(size_t)> f) {
for (size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (size_t i) {
return values[i];
}
它可以从 std::function<T(size_t)>
构造并具有 [size_t]->T
运算符。
template<class T, class Indexer=std::size_t>
using IndexResult = decltype( std::declval<T>()[std::declval<Indexer>()] );
这是一个特征,说明 v[0]
的类型结果是什么。
template<class V>
concept Vector = requires (V const& v, IndexResult<V const&>(*pf)(std::size_t)) {
typename IndexResult<V const&>;
{ V( pf ) };
{ v.size() } -> std::convertible_to<std::size_t>;
};
好了,Vector
基于 duck-type 的概念。我添加了一个 .size()
方法要求。
然后我们在所有 Vector
上写一些操作:
template<Vector V>
V operator + (const V& lhs, const V& rhs) {
return V([&] (int i) {return lhs[i] + rhs[i];});
}
template<Vector V>
std::ostream& operator<<(std::ostream& os, V const& v)
{
for (std::size_t i = 0; i < v.size(); ++i)
os << v[i] << ',';
return os;
}
修复你的基地Vect
一点点:
template<std::size_t n, typename T>
struct Vect {
Vect(std::function<T(std::size_t)> f) {
for (std::size_t i=0; i < n; i++) {
values[i] = f(i);
}
}
T values [n];
T operator[] (std::size_t i) const { // << here
return values[i];
}
constexpr std::size_t size() const { return n; } // << and here
};
然后这些测试通过:
constexpr std::size_t n = 10;
typedef double T;
MyNS::Vect<n,T> u ([&] (size_t i) {return (T)i / (T)n;});
std::cout << "u + u" << (u+u) << "\n";
(我正确地使用了名称空间,因为当我不这样做时我会感到恶心)。
请注意,operator+
是通过 ADL 找到的,因为它与 Vect
一样位于 MyNS
中。对于 MyNS
之外的类型,您必须将其 using MyNS::operator+
放入当前范围。这是故意的,几乎是不可避免的。
(如果您继承自 MyNS
中的某些内容,它也会被找到)。
...
TL;DR
概念通常应该是duck typed,这取决于你可以用类型做什么,而不是类型是如何实现的。该代码似乎并不关心您是否继承自特定类型或模板,它只是想使用一些方法;所以测试 that.
这也避免了尝试将模板参数推导到 Vect
class;我们改为从界面中提取它。