表达式模板不适用于 clang 下的原始类型重载
Expression templates not working for primitive type overloads under clang
我有一个 CRTP 基础 class 如下:
template<typename Derived, size_t DIMS>
class Base {
public:
// here is I think where the problem is
inline const Derived& self() const {return *static_cast<const Derived*>(this);}
};
那么导出的class定义为
template<typename T, size_t ... Rest>
class Derived: public Base<Derived<T,Rest...>,sizeof...(Rest)> {
public:
Derived() = default;
// This constructor binds any arbitrary expression to Derived
template<typename Expr, size_t DIMS>
inline Derived(const Base<Expr,DIMS>& src_) {
const Expr &src = src_.self();
print(src.rhs);
}
};
考虑到定义我自己的运算符,我还有以下 AddOperator
也继承自基础
template<typename TLhs, typename TRhs, size_t DIMS>
struct AddOperator: public Base<AddOperator<TLhs, TRhs, DIMS>,DIMS> {
AddOperator(const TLhs& lhs, const TRhs& rhs) : lhs(lhs), rhs(rhs) {
print(rhs);
}
const TLhs &lhs;
const TRhs &rhs;
};
那么 operator+
类型和基本类型之间的 Derived
重载 returns 只有 proxy/expression 种类:
template<typename TLhs, typename TRhs, size_t DIM0,
typename std::enable_if<!std::is_arithmetic<TLhs>::value &&
std::is_arithmetic<TRhs>::value,bool>::type = 0 >
inline AddOperator<TLhs, TRhs, DIM0>
operator+(const Base<TLhs,DIM0> &lhs, TRhs rhs) {
return AddOperator<TLhs, TRhs, DIM0>(lhs.self(), rhs);
}
但是,当我在 clang
下调用它时,我得到 AddOperator
的 rhs
的垃圾值。这是一个例子:
int main() {
Derived<double,2,2> g;
Derived<double,2,2> x = g+28;
return 0;
}
当AddOperator
中的lhs
和rhs
都是Derived
类型时的其他重载没有这个问题。
此问题仅在 clang
下发生。 gcc
编译后的代码似乎 运行 没问题。有谁知道问题出在哪里?
你的问题是你有一个悬空引用。
在 operator+
中,您按值获取 TRhs
(int
),然后构造一个 AddOperator<...>
并引用它。当 g+28
returns 时,AddOperator<...>
对象仍然引用参数 rhs
- 其生命周期现已结束。
您正在打印的垃圾是访问已销毁对象的结果 - 这是未定义的行为。在 clang 上,这表现为为您打印垃圾值,在 gcc 上它恰好有效。未定义的行为就像那样棘手。
现在,看似 "obvious" 的修复是将 operator+
更改为 rhs
参考 const
。这会将临时 28
的生命周期延长到包含调用的完整表达式的末尾 - 所以现在您的打印语句可以保证工作。至少,直到行尾。 x
构造完成后,引用将再次悬挂。
我有一个 CRTP 基础 class 如下:
template<typename Derived, size_t DIMS>
class Base {
public:
// here is I think where the problem is
inline const Derived& self() const {return *static_cast<const Derived*>(this);}
};
那么导出的class定义为
template<typename T, size_t ... Rest>
class Derived: public Base<Derived<T,Rest...>,sizeof...(Rest)> {
public:
Derived() = default;
// This constructor binds any arbitrary expression to Derived
template<typename Expr, size_t DIMS>
inline Derived(const Base<Expr,DIMS>& src_) {
const Expr &src = src_.self();
print(src.rhs);
}
};
考虑到定义我自己的运算符,我还有以下 AddOperator
也继承自基础
template<typename TLhs, typename TRhs, size_t DIMS>
struct AddOperator: public Base<AddOperator<TLhs, TRhs, DIMS>,DIMS> {
AddOperator(const TLhs& lhs, const TRhs& rhs) : lhs(lhs), rhs(rhs) {
print(rhs);
}
const TLhs &lhs;
const TRhs &rhs;
};
那么 operator+
类型和基本类型之间的 Derived
重载 returns 只有 proxy/expression 种类:
template<typename TLhs, typename TRhs, size_t DIM0,
typename std::enable_if<!std::is_arithmetic<TLhs>::value &&
std::is_arithmetic<TRhs>::value,bool>::type = 0 >
inline AddOperator<TLhs, TRhs, DIM0>
operator+(const Base<TLhs,DIM0> &lhs, TRhs rhs) {
return AddOperator<TLhs, TRhs, DIM0>(lhs.self(), rhs);
}
但是,当我在 clang
下调用它时,我得到 AddOperator
的 rhs
的垃圾值。这是一个例子:
int main() {
Derived<double,2,2> g;
Derived<double,2,2> x = g+28;
return 0;
}
当AddOperator
中的lhs
和rhs
都是Derived
类型时的其他重载没有这个问题。
此问题仅在 clang
下发生。 gcc
编译后的代码似乎 运行 没问题。有谁知道问题出在哪里?
你的问题是你有一个悬空引用。
在 operator+
中,您按值获取 TRhs
(int
),然后构造一个 AddOperator<...>
并引用它。当 g+28
returns 时,AddOperator<...>
对象仍然引用参数 rhs
- 其生命周期现已结束。
您正在打印的垃圾是访问已销毁对象的结果 - 这是未定义的行为。在 clang 上,这表现为为您打印垃圾值,在 gcc 上它恰好有效。未定义的行为就像那样棘手。
现在,看似 "obvious" 的修复是将 operator+
更改为 rhs
参考 const
。这会将临时 28
的生命周期延长到包含调用的完整表达式的末尾 - 所以现在您的打印语句可以保证工作。至少,直到行尾。 x
构造完成后,引用将再次悬挂。