我认为这个问题相当普遍,因此应该有一个已知的解决方案。我想出了一个,但我并不真正满意,所以我在这里问,希望有人可以提供帮助。
说我有一个函数,其签名是
template<typename T>
void foo(const MyArray<const T>& x);
template参数中的const可以防止更改数组内容,因为(出于此问题之外的原因)[]
的访问者(()
和MyArray<T>
)是始终标记为const,并返回对T
的引用(因此,const确保安全,因为MyArray<T>::operator[]
返回T&
,而MyArray<const T>::operator[]
返回const T&
)。>
太好了。但是,具有不同模板参数的模板是无关的,因此我无法将对MyClass<T>
的引用绑定到MyClass<const T>
的引用,这意味着我不能这样做
MyArray<double> ar(/*blah*/);
foo(ar);
请注意,在没有引用的情况下,上面的代码将可以正常工作,前提是存在一个复制构造函数,该复制构造函数使我可以从MyArray<const T>
创建MyArray<T>
。但是,我不想删除引用,因为数组构造会很多次,并且尽管相对便宜,但其成本会增加。
问题是:如何用foo
来呼叫MyArray<T>
?
到目前为止,我唯一的解决方法是:
MyArray<T> ar(/*blah*/);
foo(reinterpret_cast<MyArray<const T>>(ar));
(实际上,在我的代码中,我将重新解释转换隐藏在具有更详细名称的内联函数中,但最终结果是相同的)。类MyArray
没有针对const T
的专门化,因此无法重新解释,因此强制类型转换应为“安全”。但这并不是一个很好的阅读解决方案。一种替代方法是复制foo
的签名,使版本采用MyArray<T>
,该实现将进行强制转换并调用const版本。这样做的问题是代码重复(而且我有很多函数foo
需要重复)。
也许在foo
签名上有一些额外的模板魔术?目标是同时传递MyArray<T>
和MyArray<const T>
,同时仍保持const正确性(即,如果我不小心更改了函数体中的输入,会使编译器吠叫)。
编辑1 :类MyArray
(其实现不受我的控制)具有const访问器,因为它存储了指针。因此,调用v[3]
将修改数组中的值,但不会修改存储在类中的成员(即指针和一些类似于智能指针的元数据)。换句话说,尽管数组实际上是对象,但访问者实际上并未修改该对象。这是语义上的区别。不知道为什么他们要朝这个方向发展(我有一个主意,但是解释的时间太长了。)
编辑2 :我接受了两个答案之一(尽管它们有些相似)。我不确定(出于长期解释的原因)在我的情况下包装类是否可行(也许我必须考虑一下)。
template<typename T>
void foo(const MyArray<const T>& x);
MyArray<int> a;
foo(a);
不编译,以下代码可以编译
void foo(const MyArray<const int>& x);
MyArray<int> a;
foo(a);
注意:MyArray
确实提供了带有签名的模板化“复制构造函数”
template<typename S>
MyArray(const MyArray<S>&);
因此它可以从MyArray<const T>
创建MyArray<T>
。我感到困惑的是,为什么在T
是显式的情况下它起作用,而如果T
是模板参数却不起作用。
答案 0 :(得分:3)
我会留在
template<typename T>
void foo(const MyArray<T>&);
,并确保使用const T
实例化它(例如在unitTest中)。
否则,您可以将视图创建为std::span
。
类似的事情(取决于MyArray
提供的其他方法,您可能可以做一个更好的const视图。我目前仅使用operator[]
):
template <typename T>
struct MyArrayConstView
{
MyArrayConstView(MyArray<T>& array) : mArray(std::ref(array)) {}
MyArrayConstView(MyArray<const T>& array) : mArray(std::ref(array)) {}
const T& operator[](std::size_t i) {
return std::visit([i](const auto& a) -> const T& { return a[i]; }), mArray);
}
private:
std::variant<std::reference_wrapper<MyArray<T>>,
std::reference_wrapper<MyArray<const T>>> mArray;
};
然后
template <typename T>
void foo(const MyArrayConstView<T>&);
但是您需要显式地调用它(因为扣除不会发生,因为MyArray<T>
不是MyArrayConstView
)
MyArray<double> ar(/*blah*/);
foo(MyArrayConstView{ar});
foo<double>(ar);
答案 1 :(得分:2)
由于不允许更改MyArray,因此一种选择是使用适配器类。
template <typename T>
class ConstMyArrayView {
public:
// Not an explicit constructor!
ConstMyArrayView(const MyArray<T>& a) : a_(a) {}
const T& operator[](size_t i) const { return a_[i]; }
private:
const MyArray<T>& a_;
};
template<typename T>
void foo(const ConstMyArrayView<T>& x);
MyArray<T> x;
foo(x);
但是最后,如果您可以更改MyArray
来匹配所需的const正确性,或者切换到具有此类的类,那将是更好的选择。
答案 2 :(得分:2)
这是让函数使用一种类型的一种丑陋但有效的方法,但是还可以让编译器检查如果使用不同类型的代码,则同一代码是否可以编译:
template <typename From, typename To>
struct xfer_refs_cv
{
using type = To;
};
template <typename From, typename To>
struct xfer_refs_cv<const From, To>
{
using type = const typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<volatile From, To>
{
using type = volatile typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<From&, To>
{
using type = typename xfer_refs_cv<From, To>::type&;
};
template <typename From, typename To>
struct xfer_refs_cv<From&&, To>
{
using type = typename xfer_refs_cv<From, To>::type&&;
};
template <typename CheckType, typename Func, typename CallType>
constexpr decltype(auto) check_and_call(Func&& f, CallType&& call_arg)
noexcept(noexcept(std::forward<Func>(f)(std::forward<CallType>(call_arg))))
{
(void) decltype(std::declval<Func&&>()
(std::declval<typename xfer_refs_cv<CallType&&, CheckType>::type>()), 0){};
return std::forward<Func>(f)(std::forward<CallType>(call_arg));
}
template<typename T>
void foo(const MyArray<T>& x)
{
check_and_call<MyArray<const T>>(
[](auto&& x) {
// Function implementation here.
}, x);
}